In my code, I have a database access context that provides elementary read/write operations, called CouchDB.ctx. Various modules in my application then extend that class with additional functionality, such as Async.ctx.
I am implementing a Cache module which is wrapped around a Source module. The Cache module functions take a context argument and manipulate the database. Some calls are then forwarded to the Source module along with the context.
I need to define a functor along the lines of this:
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)
The problem is that I cannot express the fact that the context used by the Source module should be a subtype of CouchDB.ctx. The above code returns the error:
A type variable is unbound in this type declaration.
In type #CouchDB.ctx as 'a the variable 'a is unbound
How do I express this type constraint ?
[Obsolete…
The closest you can get is defining the signature as:
But of course, you could as well just write:
Edit: Note that OCaml uses structural typing for objects. That means that even if you wanted, you cannot get any more restrictive than the above. It does not even limit arguments to
getto be instances ofCouchDB.ctxor a derived class — any object that has (at least) the same methods will be compatible. Even when you writeyou can pass any object that has the same methods. The type
CouchDB.ctxis just an abbreviation for a specific structural object type that happens to match the objects generated by the class of the same name. It is not restricted to those. And just to be sure: that is considered a feature.======]
Edit 2: With the extended example, I now see what you want and why. Unfortunately, that isn’t possible directly in OCaml. You would need partially abstract types. Namely, you would need to be able to write
This is not available in OCaml. However, you can get close if you are willing to provide an explicit upcast in the signature:
Then, in
Cache, you have to replace occurrences ofctx#getwith(S.up ctx)#get, and likewise forctx#put.Note that I also made
type source = stringtransparent in the signatureSOURCE. Without that, I cannot see howctx#get sourcecan possibly type-check in theCachefunctor.