2011-07-20 14 views
5

He tropezado con un problema OCaml bastante simple, pero parece que no puedo encontrar una solución elegante. Estoy trabajando con funtores que se aplican a módulos relativamente simples (generalmente definen un tipo y algunas funciones en ese tipo) y extienden esos módulos simples al agregar funciones, tipos y módulos adicionales más complejos. Una versión simplificada sería:Módulos y campos de registro

module type SIMPLE = sig 
    type t 
    val to_string : t -> string 
    val of_string : string -> t 
end 

module Complex = functor (S:SIMPLE) -> struct 
    include S 
    let write db id t = db # write id (S.to_string t) 
    let read db id = db # read id |> BatOption.map S.of_string 
end 

No hay necesidad de dar al módulo sencilla un nombre, porque toda su funcionalidad está presente en el módulo extendido, y las funciones del módulo simples son generados por camlp4 basado en el tipo . El uso idiomático de estos funtores es:

module Int = Complex(struct 
    type t = int 
end) 

El problema aparece cuando estoy trabajando con registros:

module Point2D = Complex(struct 
    type t = { x : int ; y : int } 
end) 

let (Some location) = Point2D.read db "location" 

Parece que hay una forma sencilla de acceder a los campos x y y definido anteriormente de fuera del módulo Point2D, como location.x o location.Point2D.x. ¿Cómo puedo conseguir esto?

EDITAR: conforme a lo solicitado, aquí hay un ejemplo mínimo completa que muestra el problema:

module type TYPE = sig 
    type t 
    val default : t 
end 

module Make = functor(Arg : TYPE) -> struct 
    include Arg 
    let get = function None -> default | Some x -> (x : t) 
end 

module Made = Make(struct 
    type t = {a : int} 
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *) 
end) 

let _ = (Made.get None).a (* <-- ERROR *) 
+1

Publicar código compilable sería de gran ayuda para obtener respuestas compilables. –

Respuesta

3

En primer lugar, en su última muestra de código, última línea, es probable que significa .a en lugar de .x.

El problema con el código es que, con la forma de definir su Make funtor, el tipo es t resumen en Made: en efecto, los funtores utilizan la firma TYPE que sella {a : int} como un tipo abstracto.

El siguiente diseño evita el problema, pero, bueno, es un diseño diferente.

module type TYPE = sig 
    type t 
    val default : t 
end 

module Extend = functor(Arg : TYPE) -> struct 
    open Arg 
    let get = function None -> default | Some x -> (x : t) 
end 

module T = struct 
    type t = {a : int} 
    let default = { a = 0 } 
end 

module Made = struct 
    include T 
    include Extend(T) 
end 

let _ = Made.((get None).a) 
1

El problema es que OCaml no tiene un nombre para referirse a los componentes calificados del tipo t (en este caso un registro, pero el mismo problema estaría presente con variantes normales) fuera Made. Nombrando el nombre no resuelve el problema:

module F = struct 
    type t = {a : int} 
    let default = { a = 0 } 
end 

module Made = Make(F) 

let _ = (Made.get None).F.a (* <-- WORKS *) 

También puede declarar explícitamente el tipo fuera de la aplicación funtorial:

type rcd = {a : int} 

module Made = Make(struct 
    type t = rcd 
    let default = { a = 0 } 
end) 

let _ = (Made.get None).a (* <-- WORKS *) 
+0

Gracias por su respuesta. Podría usar su primera solución, pero estoy buscando algo más elegante que mantener un módulo 'F' solo para acceder a los campos en sí (ya que ese módulo nunca se usará para nada más). La segunda solución no funcionaría, porque 'let default' se genera y aparecería justo después de la definición del tipo, así que tendría que copiarlo manualmente en el módulo (en la práctica, en realidad son varios valores, no solo uno, entonces copiarlos es bastante detallado). –

4

Veamos la firma de algunos de los módulos implicados. Estas son las firmas generadas por Ocaml, y son las principales firmas, es decir, son las firmas más generales permitidas por la teoría.

module Make : functor (Arg : TYPE) -> sig 
    type t = Arg.t 
    val default : t 
    val get : t option -> t 
end 
module Made : sig 
    type t 
    val default : t 
    val get : t option -> t 
end 

Aviso cómo se retiene la ecuación Make(A).t = A.t (así Make(A).t es una abreviatura de tipo transparente), sin embargo, Made.t es abstracto. Esto se debe a que Made es el resultado de aplicar el funtor a una estructura anónima, por lo que no hay un nombre canónico para el tipo de argumento en este caso.

Los tipos de registros son generativos.En el nivel de la teoría de tipos subyacente, todos los tipos generativos se comportan como tipos abstractos con algo de azúcar sintáctica para constructores y destructores. La única manera de designar un tipo generativo es dar su nombre, ya sea el nombre original o uno que se expande al nombre original a través de una serie de ecuaciones de tipo.

considere lo que sucede si se duplica la definición de Made:

module Made1 = Make(struct 
    type t = {a : int} 
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *) 
    end) 
module Made2 = Make(struct 
    type t = {a : int} 
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *) 
    end) 

se obtienen dos tipos diferentes Made1.tMade2.t y, a pesar de que los lados derechos de las definiciones son las mismas. De eso se trata la generatividad.

Dado que Made.t es abstracto, no es un tipo de registro. No tiene ningún constructor. Los constructores se perdieron cuando se cerró el argumento de estructura, por falta de un nombre.

Ocurre que con los registros, uno a menudo quiere el azúcar sintáctico pero no la generatividad. Pero Ocaml no tiene ningún tipo de registro estructural. Tiene tipos de registro generativo y tiene objetos que, desde un punto de vista teórico, incluyen los registros, pero en la práctica pueden requerir un poco más de trabajo y una pequeña penalización en el rendimiento.

module Made_object = Make(struct 
    type t = <a : int> 
    let default = object method a = 0 end 
    end) 

O, si desea mantener la misma definición de tipo, es necesario proporcionar un nombre para el tipo y sus constructores, lo que significa nombrar a la estructura.

module A = struct 
    type t = {a : int} 
    let default = { a = 0 } (* <-- Generated by camlp4 based on type t above *) 
    end 
module MadeA = Make(A) 

Tenga en cuenta que si se construye Make(A) dos veces, se obtiene el mismo tipo por todas partes.

module MadeA1 = Make(A) 
module MadeA2 = Make(A) 

(Ok, esto no es notable aquí, pero Seguimos obteniendo los mismos abstractos tipos de MadeA1 y MakeA2, a diferencia del caso Made1 y Made2 anteriormente. Esto se debe a que ahora hay un nombre para estos tipos: MadeA1.t = Make(A).t.)

+0

Gran explicación. Poner un nombre a los conceptos ("generativo") ayudó enormemente, incluso si no trajo una solución elegante real al problema original. –