Combining Ocaml Functors and First class modulesPosted: November 21, 2011
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)