Modules-style interfaces

The other way to use orpc is “modules” mode. Instead of giving a protocol.x file as input, you give a protocol.ml file containing an OCaml signature consisting of type, exception, and module type definitions (where the module type definitions contain only value declarations). In addition to being orpc input, the protocol.ml file is also compiled into clients and servers.

The clients and servers generated from a modules input are similar to the ones generated by ocamlrpcgen, but the Protocol_aux module doesn’t contain type or exception definitions (they are defined in Protocol); Protocol_clnt additionally contains implementations of the module types in Protocol; and Protocol_srv additionally contains binding functors that take the module types as arguments.

There are three kinds of module type you can give in an input file, Sync, Async, and Lwt (see Lwt). You indicate which one by the name of module type. For example:

type foo = int * int
exception Bar of string

module type Sync =
sig
  val baz : foo -> int
end

module type Async =
sig
  val baz : foo -> ((unit -> int) -> unit) -> unit
end

module type Lwt =
sig
  val baz : foo -> int Lwt.t
end

In an input file you can give any combination of these kinds, but they must agree on the arguments and return type (modulo the differences in interface); appropriate modules and functors are generated for the kinds you give.

For this input, a Protocol_clnt module with the following signature is generated:

  (* ... the same stuff as in the simple interface .. *)
module Sync (C : sig val with_client : (Rpc_client.t -> 'a) -> 'a;; end) :
  Protocol.Sync;;
module Async (C : sig val with_client : (Rpc_client.t -> 'a) -> 'a;; end) :
  Protocol.Async;;
module Lwt (C : sig val with_client : (Rpc_client.t -> 'a) -> 'a;; end) :
  Protocol.Lwt;;

The idea here is that you can get a module implementing the interface for a kind by passing a module that can produce clients to the functor for that kind.

A Protocol_srv module with the following signature is generated:

  (* ... the same stuff as in the simple interface .. *)
module Sync (A : Protocol.Sync) :
  sig
    val bind :
      ?program_number: Rtypes.uint4 ->
        ?version_number: Rtypes.uint4 -> Rpc_server.t -> unit;;
  end;;
module Async (A : Protocol.Async) :
  sig
    val bind :
      ?program_number: Rtypes.uint4 ->
        ?version_number: Rtypes.uint4 -> Rpc_server.t -> unit;;
  end;;
module Lwt (A : Protocol.Lwt) :
  sig
    val bind :
      ?program_number: Rtypes.uint4 ->
        ?version_number: Rtypes.uint4 -> Rpc_server.t -> unit;;
  end;;

The idea here is that you can bind a module implementing the interface for a kind by passing it to the functor for that kind and calling the bind function.

Abstract module kind

Another way to specify an interface is with a module type where the return type is abstract.

type foo = int * int
exception Bar of string

module type Abstract =
sig
  type 'a _r
  val baz : foo -> int _r
end

module type Sync = Abstract with type 'a _r = 'a
module type Async = Abstract with type 'a _r = ((unit -> 'a) -> unit) -> unit
module type Lwt = Abstract with type 'a _r = 'a Lwt.t

This is convenient because you don’t have to repeat all the functions if you use more than one kind. It can also be useful if you want to abstract over the kind of interface; the Abstract module type gives you a common signature for all kinds. (For instance, there could be just one tracing module that worked with all kinds, although this is not currently implemented.)

The return type must be named _r, and the specific kinds must be declared exactly as shown. In the server implementations you also need to include the appropriate _r declaration. See the modules example.