Sign in to follow this  

[OCaml] Clever Messaging

This topic is 3740 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am facing the issue of implementing a message system between game components in OCaml. The problem here is the Open/Closed principle: I want to be able to create a basic component without specifying all the messages that it can send or receive. In short, I need to write the following in a class:
type message = (* clever definition *)

class abstract component = 
object
  method virtual react : message
end
And never worry about changing the definition of a message again. Intended behaviour is that bjects can send each other the messages of their choice (I can live with some constraints on what messages actually are, as long as I can create new kinds of messages without altering the above definition). If an object doesn't know how to interpret a message, it raises an exception. My first approach was to use an exception-throwing approach: a message is fundamentally an unit -> unit function which raises an exception, perhaps with some additional generic information around it (such as the sender). In short:
(* The basic definition *)
type message = unit -> unit

class abstract component = 
object
  method virtual react : message -> unit
end

(* An example class with a custom message *)
exception MyPrintMessage of string
let myPrintMessage str () -> raise (MyPrintMessage str)

class printer = object
  inherit component
  method react msg = 
    try msg () with
      | MyPrintMessage s -> Printf.printf "Message: %s\n" s
      | n -> raise n
end

(* Sending a message to a 'component' *)
let c = 
  (new printer) :> component
in
c # react (myPrintMessage "Hello, world!")
The messages are clean, and so is their syntax, and they can also be easily serialized and sent over a network. However, I'm worried about the fact that this isn't very elegant (it's using exceptions at cross-purpose) and might incur performance penalties because of the exception 'walk back up the stack' process. Any ideas on insights about either this method or an alternate open-closed method?

Share this post


Link to post
Share on other sites
Let's see if I understand correctly...

  • You want to be able to add messages easily without having to change one central definition, meaning you can't use variants.

  • You want your messages to carry information, so you can't use ints (problems with choosing unique values for separate messages) of strings (well you could use strings and parse them, and no type safety), but these are hardly robust solutions.

  • You can't use records because AFAIK there's no subtyping relationship between them in O'Caml, i.e. { x:int } and { x:int; s:string } are not related.

  • You could implement a kind of dynamic record yourself, i.e. type dynamic_record = (string * object) list with object a variant enumerating all possible types for you to use, but a number of problems come with it (no type safety, objects definition m.

  • Using classes as objects should work, but you'd need to emulate multiple dispatch (visitors? I'm not familiar enough with classes/objects in O'Caml to know whether or not that's possible).

  • So, that leaves exceptions, which are in a way perfect: they can carry their own extra baggage, subtyping relations don't matter, there's a matching-construct already waiting for you, uniqueness is guaranteed, everything is type-safe... But using them for this kind of thing is icky, and they're possibly slow.



If I were in that kind of trouble, I would start thinking metaprogramming (it seems that's my answer for everything these days...). With CamlP4 (or whatever, you could use Perl too in theory) I would try to build new abstractions for messages: a message declaration (message print_message of string) and a message-matching constructor (msg_match msg | print_message str -> ...). As a first implementation, message might simply be translated into exception and msg_match into a try-block. A better solution would probably be to use variants: the preprocessor collects all message declarations and puts them into one big variant, etc. To me, it seems that this would be the most natural O'Caml solution.

Overkill? :)

Share this post


Link to post
Share on other sites
After some more experiments, it seems that there is a possible usage of polymorphic variants, as:

class virtual ['message] component = 
object
method virtual send : 'message -> unit
end

class ['message] printer =
object
inherit 'message component
method send = function
| `Print s -> print_endline s
| _ -> raise UnknownMessage
end

(new printer) # send (`Print "Hello, world!")


This has the ugly side-effect of not having any namespacing (that is, once someone somewhere defined `Print, nobody else is allowed to use that name ever) which does not exist with exceptions (which can be disambiguated using module names). Plus, it needs the 'message type variable everywhere for resolution purposes.

Ultimately, I remembered that exceptions can be manipulated and matched without requiring a throw, so:

type message = { sender : id; payload : exn }

class virtual component =
object
method virtual send : message -> unit
end

module Print = struct
exception MsgPrint of string

class printer =
object
inherit component
method send msg =
match msg.payload with
| MsgPrint s -> print_endline s
| _ -> raise UnknownMessage
end
end

(new Print.Printer) # send {
sender = world;
payload = Print.MsgPrint "Hello, world"
}

Share this post


Link to post
Share on other sites

This topic is 3740 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this