Dev (Back & Front)ARTIGO

Combinando functors em OCaml e módulos First Class

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/

 

É um cientista da computação com interesse em todas as facetas da estratégia do desenvolvimento de software, e uma atração especial para desafios algorítimicos da computação distribuída. Estando convencido de que uma linguagem de programação deve ser parte da solução, em vez de parte do problema, ele foi bem sucedido ao tornar o C++ uma linguagem praticamente extinta na Incubaid e na Amplidata, substituindo-o por algo menos inadequado (OCaml). Atualmente, trabalha como pesquisador para a Incubaid.

Ver perfil