2012-05-14 14 views
5

In meinem Code habe ich einen Datenbankzugriff Kontext, die elementare Lese-/Schreibvorgänge bietet, genannt CouchDB.ctx. Verschiedene Module in meiner Anwendung erweitern diese Klasse dann um zusätzliche Funktionen wie Async.ctx.OCaml: Typ Einschränkungen in Signaturen

Ich implementiere ein Cache Modul, das um ein Source Modul gewickelt ist. Die Modulfunktionen Cache nehmen ein Kontextargument und manipulieren die Datenbank. Einige Anrufe werden dann zusammen mit dem Kontext an das Modul Source weitergeleitet.

Ich brauche eine Funktors entlang der Linien dieses zu definieren:

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) 

Das Problem ist, dass ich nicht auf die Tatsache ausdrücken, daß der Kontext durch die Source Modul verwendet wird, soll ein Subtyp von CouchDB.ctx sein. Der obige Code gibt den Fehler zurück:

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

Wie kann ich diese Typabhängigkeit ausdrücken?

+0

Ich bin neugierig auf Ihre 'SOURCE' Signatur. Die Deklaration in meinem Kopf sollte 'Modultyp sein SOURCE = sig class ctx: object erben CouchDB.ctx end (* ... *) end'; Tut das nicht, was Sie brauchen? –

Antwort

5

[Obsolete ...

Die nächstgelegene Sie bekommen kann, ist die Definition der Signatur als:

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

Aber natürlich könnte man auch einfach schreiben:

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

bearbeiten : Beachten Sie, dass OCaml strukturelle für Objekte eingeben verwendet. Das bedeutet, dass Sie, selbst wenn Sie es wollten, nicht restriktiver als die oben genannten werden können. Es beschränkt nicht einmal die Argumente auf get auf Instanzen von CouchDB.ctx oder einer abgeleiteten Klasse - jedes Objekt, das (mindestens) die gleichen Methoden hat, ist kompatibel. Auch wenn Sie

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

schreiben Sie jede Objekt übergeben, die die gleichen Methoden hat. Der Typ CouchDB.ctx ist nur eine Abkürzung für einen bestimmten Strukturobjekttyp, der zufällig mit den Objekten übereinstimmt, die von der Klasse mit dem gleichen Namen generiert wurden. Es ist nicht auf diese beschränkt. Und nur um sicher zu sein: Das ist ein Feature.

======]

Edit 2: Mit dem erweiterten Beispiel habe ich jetzt sehen, was Sie wollen und warum. Leider ist das in OCaml nicht direkt möglich. Sie würden teilweise abstrakte Arten benötigen. Sie müssten nämlich in der Lage sein zu schreiben

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

Dies ist nicht in OCaml verfügbar. Sie können jedoch nahe kommen, wenn Sie bereit, um eine explizite upcast in der Signatur sind:,

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

in Cache Dann haben Sie Vorkommen von ctx#get mit (S.up ctx)#get, ersetzen und ebenfalls für 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) 

Bitte beachte, dass ich auch type source = string transparent SOURCE in der Signatur gemacht. Ohne das kann ich nicht sehen, wie ctx#get source kann möglicherweise überprüfen Sie die Cache Funktor.

+0

Ich bekomme nicht, wie ich das erste Beispiel zu meiner Situation passen könnte (meine Kontextklasse hat Null-Parameter). Die zweite Version ist inkorrekt, da sie erwarten würde, dass 'Source.get' * irgendeine * Unterklasse von' CouchDB.ctx' akzeptiert, während sie tatsächlich nur * one * ('Async.ctx') akzeptiert. –

+0

Nun, warum willst du 'Source.get' so einschränken? Soweit ich sehen kann, ist das nicht notwendig, noch kauft es irgendetwas (alle Untertypen sind in OCaml rein strukturell, selbst wenn Sie Klassennamen verwenden). Ich würde empfehlen, nicht zu versuchen, eine Form der nominellen Untertypisierung auf OCaml zu erzwingen, da die Sprache nicht so funktioniert. OCaml favorisiert Strukturtypisierung gegenüber Nominal und Polymorphismus gegenüber Subtypisierung. Beschränken Sie Typen nur, wenn dies für die Typprüfung erforderlich ist. –

+0

Dieser Typ wurde für Illustrationszwecke * eingeschränkt. In meiner tatsächlichen Codebasis wurde diese Einschränkung vom Körper der Funktion abgeleitet, die offensichtlich komplexer ist als das, was hier dargestellt wird. Ich versuche, mein Problem darzustellen, ohne die tatsächlichen Tausende von Zeilen aus meinem Code kopieren zu müssen. Da die Verwendung von Typabhängigkeiten zu Illustrationszwecken verwirrend ist, habe ich den obigen Code zur weiteren Erläuterung bearbeitet. –

1

Es sei denn, ich bin Missverständnis, was Sie nach dann sollte dies den Trick:

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

class ctx : CouchDB.ctx ein class-specification ist. Die OCaml docs beschreiben sie als

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.

Es gibt auch diese

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

, die auf subtile Weise anders ist. Das erstere erfordert eine echte Klassendefinition in dem Modul, wobei das letztere eine Klassendefinition oder eine Klassentypdefinition akzeptiert (d. H. Einen Klassentypalias).

+0

Ich habe meine Frage aktualisiert, um ein Beispiel für die Verwendung des Funktors einzuschließen, so dass Sie es auch kompilieren können. Ihre Lösungen verhinderten, dass 'SomeSource' die' SOURCE'-Signatur abgleicht, da sie 'ctx = Async.ctx' weder als Klassen noch als Klassenarten zulassen. –