[zeromq-dev] Introducing Caravan: Simple, Faithful ZMQ-Bindings for Objective Caml
Guillaume Yziquel
guillaume.yziquel at citycable.ch
Mon Apr 4 03:09:25 CEST 2011
Le Monday 04 Apr 2011 à 01:04:22 (+0100), Pedro Borges a écrit :
> Hi,
>
> I'm the author of ocaml-zmq
>
> On Sun, Apr 3, 2011 at 6:33 PM, Brian Ledger <bpl29 <at> cornell.edu> wrote:
>
> > The exemplifying problem in this domain is how to integrate getsockopt and setsockopt into our interface. OCaml-ZMQ solves this problem by fragmenting the functions into a set of 21 separately named functions. Each function has the appropriate argument and result types, so it is impossible for the client to pass data of the incorrect type to the system.
>
> > Caravan solves this problem more elegantly by using an opaque option type. The option type abstracts the get and set functions of each option, and each option > is instantiated with the appropriate safety measures for the client. This keeps intact the original getsockopt and setsockopt functions with their original arguments both ordered and correct. All that, and the entire mechanism is invisible to the client!
>
> I must say I like your approach very much, however in some cases
> having 21 methods allows the user to write the code in a more
> declarative fashion for example "subscribe sock ..." vs "setsockopt
> sock subscribe ..." or "has_more sock" vs "getsockopt sock rcvmore".
OCaml lacks GADTs. This seems to work around it. Which is good.
However, I do not understand why you need the unit -> prefix in the type
declaration
type 'a socket_option = (unit -> socket -> 'a) * (unit -> socket -> 'a -> unit)
I do not see side effects that would justify it. For instance at line
167 of ZMQ.ml:
let getsockopt (sock: socket) (option: 'a socket_option) : 'a =
(fst option) () sock
there doesn't seem to be a specific side-effect.
I'd just scrape off the line 82 let thunk x () = x function and do
without.
Concerning line 83, let commute f x y = f y x, it seems necessary only
because of the way you write at line 88 and 89
let getsockopt' option = thunk ((commute getsockopt'') option)
You can avoid the parenthesis around (commute getsockopt'') and rewrite
let getsockopt' option () sock = getsockopt'' sock option
At first glance. Eta-expand instead of allocating closures on the heap.
Maybe I'm missing something. Dunno.
> > No Build Dependencies
> > Other than the obvious ZMQ installation, Caravan does not require any additional libraries to run. This is in contrast to the OCaml-ZMQ project, which uses an unnecessary Uint library to handle unsigned integer options.
>
> Well using Uint keeps the user from passing negative arguments plus
> respects the type of the original options so I thought it was
> necessary. Why do you think it is unnecessary?
It's simply that it would be nice to have unsigned ints handled directly
in the stdlib... but that's another story.
> > Error Handling
>
> > The ZMQ guide for writing bindings states "The binding should use [the] standard error mechanism of the language, whether it is error codes, exceptions, etc." The standard error mechanism in OCaml is the 'try-raise-with' clause. This obviates the need for zmq_errno, return codes, and cumbersome error frameworks. However, OCaml-ZMQ is implemented with error codes anyway.
>
> > Caravan maps the POSIX error codes to a series of distinct OCaml exceptions, which are raised in C when an error is detected. Furthermore, at this granularity, it was possible to specify for each exception it's function-specific message as it was declared in the API. This means that exceptions are more informative, and can be handled using the natural syntax of OCaml's exception mechanism.
>
> Agreed, revamping exceptions is actually in the roadmap.
It's arguably nice to throw exceptions around in OCaml. But a bit risky
since catching them is somehow not type-checked. I'm fine with throwing
exceptions, though I'd prefer option types, or adequate algebraic
datatypes.
> Besides the previous diferences there is another one between the two
> bindings: ocaml-zmq uses phantom types to prevent the user from
> calling subscribe, unsubscribe and create devices with the wrong kind
> of socket, while Caravan just allows that to happen. Why am I bringing
> up phantom types? Because the phantom types is really the only thing
> keeping both both bindings from being merged. If the users feel there
> is no real need for the extra type safety, I'll gladly drop the
> phantom types, and I'll offer myself to merge the bindings.
It's nice to have phantom types to have usage of sockets type checked.
But if it prevents them from being bundled in an array for zmq_poll(),
there has to be a workaround.
One would be to have them carrying their types around at runtime. Not so
lean. Another would be to have a declaration like
type 'a typed_socket = private untyped_socket
allowing you to subtype a typed socket into an untyped_socket, but not
the other way round as that would not be safe. Another would be brutally
to offer conversion functions from one to the other. Another one would
be to be able to do something like:
type phantom_type1
type phantom_type2
type 'a compiletime_typed_socket
type runtime_typed_socket =
| Type1 of phantom_type1 compiletime_typed_socket
| Type2 of phantom_type2 compiletime_typed_socket
and have poll declared as:
val poll : runtime_typed_socket array -> ?timeout: int -> unit -> ...
There's no perfect solution to this kind of dilemna when writing
bindings.
I'd go for the '= private' solution as it's the only one that follows
ZeroMQ's typing. One type for all sockets in C. Then refined with a
private type abbreviation to incorporate OCaml static typing.
I'd like to have sockets statically typed, but I'd also rather have
them untyped than nothing.
> Best regards
>
> Pedro Borges
--
Guillaume Yziquel
More information about the zeromq-dev
mailing list