Combining Ocaml Functors and First class modules
Posted: November 21, 2011 Filed under: OCaml, Programming | Tags: OCaml 5 Comments »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.

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.
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}“.
Yes, that’s it. thx.
I’ll leave the comment in the code,
otherwise people won’t know what you were talking about.
> 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.
Yes you’re right. I meant to say “objects and closures”. I should reread more carefully before hitting the ‘publish’ button.