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/