Temos em nossa base de código de um módulo de benchmark feito em forma de functor para que possamos mudar a implementação do módulo. O benchmark em si é parte de uma configuração que possui um script de controle e um pequeno servidor. O driver executa o benchmark com vários parâmetros em diferentes configurações de hardware e em seguida envia os resultados para o servidor que processa os resultados e produz gráficos, tabelas, comparações, e assim por diante.
Nós também queremos deixar a configuração do driver determinar a implementação, e isso sem recompilação. O Ocaml possui módulos first class (desde 3.12), por isso deve ser possível configurá-lo. Depois de fazer algumas experimentações, nós terminamos com uma combinação de functors e módulos first class.
Um pequeno exemplo irá esclarecer a solução:
module type Beverage = sig type t val pour : unit -> t val consume : t -> unit end
Este é o tipo de módulo que é necessário para o 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
Aí vem o functor fazendo bom uso do tipo de módulo definido acima.
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
Terminamos com a parte que faz a seleção do módulo que desejamos usar.
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
();;
Na verdade, isso me faz pensar se esse problema não seria resolvido de forma mais elegante usando uma classe e alguma refatoração.
Vamos tentar isso.
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
;;
Para essa coisa em particular, parece que os objetos estão cooperando mais do que os módulos. O importante é que você pode alcançar esse tipo de composição tanto com os módulos quanto com as classes, embora isso possa não ser muito elegante em todos os casos.
Parece que classes e módulos possuem uma relação semelhante como closures e módulos, o que me faz lembrar uma boa citação.
O venerável mestre Qc Na estava caminhando com seu aluno, Anton. Com a esperança de levar o mestre a uma discussão, Anton disse: “Mestre, eu ouvi dizer que objetos são algo muito bom – isso é verdade?” Qc Na olhou com pena para o seu aluno e respondeu: “Aluno tolo – objetos são apenas closures de um homem pobre.”
Castigado, Anton despediu-se de seu mestre e voltou para sua cela, com a intenção de estudar closures. Ele leu atentamente todo o “Lambda: The Ultimate…”, uma série de artigos e similares, e implementou um pequeno interpretador Scheme com um sistema de objetos baseados em closure. Ele aprendeu muito, e aguardava ansiosamente para informar o seu mestre de seu progresso.
Em sua próxima caminhada com Qc Na, Anton tentou impressionar seu mestre, dizendo: “Mestre, eu estudei diligentemente o assunto e, agora, entendo que os objetos são verdadeiramente closures de um homem pobre.” Qc Na responderam acertando Anton com sua bengala, dizendo: “Quando você vai aprender? Closures são objeto de um homem pobre. “Naquele momento, Anton tornou-se iluminado.
(peguei daqui)
***
Texto original disponível em http://blog.incubaid.com/2011/11/21/combining-ocaml-functors-and-first-class-modules/



