2012-05-14 11 views
5

En mi código, tengo un contexto de acceso a la base de datos que proporciona operaciones elementales de lectura/escritura, llamadas CouchDB.ctx. Varios módulos en mi aplicación luego amplían esa clase con funcionalidad adicional, como Async.ctx.OCaml: escriba constraints in signaturas

Estoy implementando un módulo Cache que está envuelto alrededor de un módulo Source. Las funciones del módulo Cache toman un argumento de contexto y manipulan la base de datos. Algunas llamadas se envían al módulo Source junto con el contexto.

I necesidad de definir un funtor a lo largo de las líneas de esto:

module CouchDB = struct 
    class ctx = object 
    method get : string -> string option monad 
    method put : string -> string -> unit monad 
    end 
end 

module AsyncDB = struct 
    class ctx = object 
    inherit CouchDB.ctx 
    method delay : 'a. float -> (ctx -> 'a monad) -> 'a monad 
    end 
end 

module type SOURCE = sig 
    class ctx = #CouchDB.ctx (* <-- incorrect *) 
    type source 
    val get : source -> ctx -> string monad 
end 

module Cache = functor(S:SOURCE) -> struct 
    class ctx = S.ctx 
    type source = S.source 
    let get source ctx = 
    bind (ctx # get source) (function 
    | Some cache -> return cache 
    | None -> 
     bind (S.get source ctx) 
     (fun data -> bind (ctx # put source data) 
         (fun() -> return data)) 
end 

module SomeSource = struct 
    class ctx = AsyncDB.ctx 
    type source = string 
    let get s ctx = 
    ctx # async 300 (some_long_computation s) 
end 

module SomeCache = Cache(SomeSource) 

El problema es que no puedo expresar el hecho de que el contexto utilizado por el módulo Source debe ser un subtipo de CouchDB.ctx. El código anterior devuelve el error:

A type variable is unbound in this type declaration. 
In type #CouchDB.ctx as 'a the variable 'a is unbound 

¿Cómo expreso esta restricción de tipo?

+0

Tengo curiosidad sobre su firma 'SOURCE'. La declaración en mi mente debería ser 'module type SOURCE = sig class ctx: objeto hereda CouchDB.ctx end (* ... *) end'; ¿Esto no hace lo que necesitas? –

Respuesta

5

[obsoleto ...

Lo más cerca que se puede obtener es la definición de la firma como:

module type SOURCE = sig 
    type 'a ctx = 'a constraint 'a = #CouchDB.ctx 
    type source 
    val get : source -> 'a ctx -> string 
end 

Pero, por supuesto, puede que también acaba de escribir:

module type SOURCE = sig 
    type source 
    val get : source -> #CouchDB.ctx -> string 
end 

Editar : Tenga en cuenta que OCaml usa estructural escribiendo para objetos. Eso significa que incluso si quisiera, no puede obtener nada más restrictivo que el anterior. Ni siquiera limita los argumentos a get para que sean instancias de CouchDB.ctx o una clase derivada: cualquier objeto que tenga (al menos) los mismos métodos será compatible. Incluso cuando se escribe

val get : source -> CouchDB.ctx -> string 

puede pasar cualquier objeto que tiene los mismos métodos. El tipo CouchDB.ctx es solo una abreviatura para un tipo de objeto estructural específico que coincida con los objetos generados por la clase del mismo nombre. No está restringido a esos. Y solo para estar seguro: eso se considera una característica.

======]

Edición 2: Con el ejemplo extendido, ahora veo lo que quiere y por qué. Desafortunadamente, eso no es posible directamente en OCaml. Necesitaría tipos parcialmente abstractos. A saber, necesitaría poder escribir

module type SOURCE = sig 
    type ctx < CouchDB.ctx 
    ... 
end 

Esto no está disponible en OCaml. Sin embargo, se puede obtener cerca si están dispuestos a proporcionar una conversión hacia arriba explícita en la firma:

module type SOURCE = sig 
    type ctx 
    val up : ctx -> CouchDB.ctx 
    type source = string 
    val get : source -> ctx -> string monad 
end 

Luego, en Cache, hay que reemplazar las ocurrencias de ctx#get con (S.up ctx)#get, y lo mismo para ctx#put.

module Cache = functor (S:SOURCE) -> struct 
    type ctx = S.ctx 
    type source = S.source 
    let get source ctx = 
    bind ((S.up ctx)#get source) ... 
end 

module SomeSource = struct 
    type ctx = AsyncDB.ctx 
    let up ctx = (ctx : ctx :> CouchDB.ctx) 
    type source = string 
    let get s ctx = ... 
end 

module SomeCache = Cache (SomeSource) 

Tenga en cuenta que también hice type source = string transparente en la firma SOURCE. Sin eso, no puedo ver cómo ctx#get source puede verificar el Cache functor.

+0

No entiendo cómo podría hacer que el primer examen se ajuste a mi situación (mi clase de contexto no tiene parámetros). La segunda versión es incorrecta, ya que esperaría que 'Source.get' aceptara * cualquier * subclase de' CouchDB.ctx', cuando en realidad solo acepta * one * ('Async.ctx'). –

+0

Bueno, ¿por qué * quieres * restringir 'Source.get' de esa manera? Por lo que puedo ver, eso no es necesario, ni compra nada (todo el subtipado es puramente estructural en OCaml, incluso si usa nombres de clase). Yo recomendaría no tratar de forzar alguna forma de subtipado nominal en OCaml, ya que no es así como funciona el lenguaje. OCaml favorece el tipado estructural sobre nominal y el polimorfismo sobre el subtipado. Solo restrinja los tipos si eso es necesario para la verificación de tipos. –

+0

Ese tipo se limitó para * fines de ilustración *. En mi base de código real, esa restricción se dedujo del cuerpo de la función, que obviamente es más compleja que la que se presenta aquí. Estoy tratando de presentar mi problema sin copiar y pegar las miles de líneas reales de mi código. Como el uso de restricciones de tipo con fines ilustrativos es confuso, he editado el código anterior para obtener una aclaración adicional. –

1

a menos que esté entendiendo mal lo que buscas, entonces esto debe hacer el truco:

module type SOURCE = sig 
    class ctx : CouchDB.ctx 
    type source 
    val get : source -> ctx -> string 
end 

class ctx : CouchDB.ctx es una class-specification. Los documentos OCaml los describen como

This is the counterpart in signatures of class definitions. A class specification matches a class definition if they have the same type parameters and their types match.

Hay también esta

module type SOURCE = sig 
    class type ctx = CouchDB.ctx 
    type source 
    val get : source -> ctx -> string 
end 

que es sutilmente diferente. El primero requiere una definición de clase real en el módulo que este último acepta una definición de clase o una definición de tipo de clase (es decir, un alias de tipo de clase).

+0

He actualizado mi pregunta para incluir un ejemplo de uso del functor, para que también pueda compilarlo. Sus soluciones impidieron que 'SomeSource' coincida con la firma' SOURCE', porque no permiten 'ctx = Async.ctx' como clases o clases. –