The protocol compiler generates objects which is somewhat rare in modern OCaml code. Are objects simply the best fit for RPC or is there another reason?
Conceptually, Cap’n Proto is object-oriented, for a few reasons:
- You don’t control remote objects, and must assume they are stateful.
- In the object-capability model, services are always free to delegate. If you ask for a
foo service from Bob, you might get a service hosted at Bob, at Carol or even one you host yourself.
- Pipelining means that you can treat a promise of a service like the service itself. In particular, you can invoke methods on it while it’s still unresolved.
Therefore, there will always be dynamic dispatch needed.
Of course, you can still write OO code without using OCaml’s OO features. In this library, I use functional style for the client side, but use the OO syntax for defining services. The main difficulty with OCaml OO is that it often has to infer all the types, which can lead to complex compiler error messages. Here though, the service implementation inherits from a generated base-class with the types, so that isn’t a problem.
For example, the tutorial defines a how to create a local logging service from a plain function roughly like this (simplifying the implementation of the
let local (fn : string -> unit) =
Api.Service.Callback.local @@ object
method log request = fn request.msg
If done with modules instead, it would look something like:
module Callback : Api.Service.Callback.S with
type t = (string -> unit) =
type t = string -> unit
let log t request = t request.msg
let local fn =
Api.Service.Callback.local (module Callback) fn
Either works, but the first seems more natural to me.
Also, the schema language allows inheritance, which we might want to support in the future (the OCaml plugin doesn’t currently support this).
I noticed somewhat long module names: Capnp_rpc_unix. This is just a matter of taste, but could this be structured as Capnp_rpc.Unix to let the module system do the work?
That would put the Unix code in the same package as the core code, which would prevent it from working on non-Unix systems (e.g. Mirage/Xen). Many OCaml libraries are structured like this (e.g.
Lwt_unix) for the same reason.
You can, of course, create your own aliases as you please. The tutorial uses the full name in many places because I wanted to be clear about where the modules were coming from.
Note that the Unix module should only be needed in your
main.ml to set things up. The rest of the code should use the core library, which I suggest