Вопрос

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:

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

But of course, you could as well just write:

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

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 get to be instances of CouchDB.ctx or a derived class -- any object that has (at least) the same methods will be compatible. Even when you write

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

you can pass any object that has the same methods. The type CouchDB.ctx is 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

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

This is not available in OCaml. However, you can get close if you are willing to provide an explicit upcast in the signature:

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

Then, in Cache, you have to replace occurrences of ctx#get with (S.up ctx)#get, and likewise for 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)

Note that I also made type source = string transparent in the signature SOURCE. Without that, I cannot see how ctx#get source can possibly type-check in the Cache functor.

Другие советы

Unless I'm misunderstanding what you're after then this should do the trick:

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

class ctx : CouchDB.ctx is a class-specification. The OCaml docs describe them as

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.

There's also this

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

which is subtly different. The former requires a real class definition in the module which the latter accepts a class definition or a classtype definition (i.e. a class type alias).

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top