Back-End

13 set, 2012

Combinando functors em OCaml e módulos First Class

Publicidade

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/