Combining Ocaml Functors and First class modules

We have in our codebase a benchmark module that’s functorized so that we can change the implementation of the module. The benchmark itself is part of a setup that has a driver script and a little server. The driver runs the benchmark with various parameters on different hardware setups, and then posts the results to the server that processes the results and produces graphs, tables, comparisons aso.

But we also want to let the configuration of the driver determine the implementation, and this without recompilation. Ocaml has first class modules (since 3.12), so it must be possible to set it up. After a bit of experimentation, we ended up with a combination of functors and first class modules.

A small example will clarify the solution:

 module type Beverage = sig  
   type t
   val pour     : unit -> t
   val consume  : t -> unit
 end

This is the module type that is needed for the functor.

module Beer = struct 
  type t = BEER

  let pour () =  
    let () = Printf.printf "... a nice head ... " in 
    BEER

  let consume t = Printf.printf " Ha! Nothing like a good beer to quench the thirst\n"
end
module Whisky = struct 
  type t = WHISKY

  let pour () = 
    let () = Printf.printf "... 2 fingers of this ...." in 
    WHISKY

  let consume _ = Printf.printf "... Ha! Piss of the Gods!\n"
end 

Here comes the functor making good use of the module type defined above.


module Alkie = functor (B:Beverage) -> struct

  let rec another = function
    | 0 -> ()
    | i -> let b = B.pour () in 
	   let () = B.consume b in
	   another (i-1)
  
end

We end with the part that does the selection of the module we want to use.

let menu(* : (string, module Beverage) Hashtbl.t (* this should work, but it doesn't *) *) = Hashtbl.create 3
let () = Hashtbl.add menu "Beer"   (module Beer   : Beverage) 
let () = Hashtbl.add menu "Whisky" (module Whisky : Beverage) 

let () =
  let favourite = ref "Whisky" in
  let () = Arg.parse
    [("--beverage", Arg.Set_string favourite, Printf.sprintf "which beverage to use (%s)" !favourite)] 
    (fun s -> raise (Arg.Bad s))
    ("usage:")
  in
  let module B = (val (Hashtbl.find menu !favourite) : Beverage) in
  let module AlkieB = Alkie(B) in
  let () = AlkieB.another 20 in
  ();;

Actually, this makes me wonder if this problem would not be more elegantly solved using a class type and some factories.
Let's try that.

class type beverage = object
  method consume : unit -> unit
end

class beer = object (self:#beverage)
  method consume () = Printf.printf "... beer\n"
end

let make_beer () = 
  let () = Printf.printf "pouring beer\n" in
  let b = new beer in
  (b:> beverage)

class whisky = object(self:#beverage)
  method consume () = Printf.printf "...drank whisky\n"
end

let make_whisky () =
  let () = Printf.printf "pouring whisky\n" in
  let b = new whisky in
  (b:> beverage)

class alkie p = object (self)
  method another n = 
    if n = 0 
    then () 
    else
      let b = p () in
      let () = b # consume () in
      self # another (n-1)
end

let menu = Hashtbl.create 3 
let () = Hashtbl.add menu "Beer" make_beer
let () = Hashtbl.add menu "Whisky" make_whisky

let () = 
  let favourite = ref "Whisky" in
   let () = Arg.parse
    [("--beverage", Arg.Set_string favourite, Printf.sprintf "which beverage to use (%s)" !favourite)] 
    (fun s -> raise (Arg.Bad s))
    ("usage:")
   in
   let p = Hashtbl.find menu !favourite in
   let a = new alkie p in
   a # another 20
;;

For this particular thing, It seems objects are more cooperative than modules.
The important part is you can achieve this kind of composition both with modules and classes, although it may not be very elegant in all cases.
It seems classes and modules have a comparable relationship as closures and modules, which reminds me of a good quote.

The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures."

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.

(nicked from here)

have fun,

Romain.


6 Comments on “Combining Ocaml Functors and First class modules”

  1. J. Prevost says:

    Glancing at the ocaml docs and examples, I see the problem. The syntax specifies that a module type be written “(module )”, but the example uses “module “. If you change your commented out line to:

    let menu : (string, (module Beverage)) Hashtbl.t = Hashtbl.create 3

    then it works just fine.

  2. J. Prevost says:

    Whoops. I used angle brackets. Let’s try again:

    The syntax specifies that a module type be written “(module {package-type})”, but the example uses “module {package-type}“.

  3. gasche says:

    > It seems classes and modules have a comparable relationship as closures and modules

    You probably mean “closure and objects”.

    Module and classes have common aspects, but are quite different on the edges. Modules don’t have the open recursion aspect of inheritance (you can include a module in another module to extend it, but overriden definitions won’t be magically used in the old module calls), and object cannot carry type components.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.