OCaml:签名中的类型约束

6
在我的代码中,我有一个名为CouchDB.ctx数据库访问上下文,提供基本的读写操作。我的应用程序中的各种模块使用该类扩展其他功能,例如Async.ctx
我正在实现一个围绕Source模块包装的Cache模块。Cache模块函数需要一个上下文参数,并操作数据库。然后,一些调用将随着上下文转发到Source模块。
我需要定义一个类似于以下内容的函数:
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)

问题在于我无法表达Source模块使用的上下文应该是CouchDB.ctx的子类型。以上代码会返回以下错误:

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

我该如何表达这种类型约束?

我对你的 SOURCE 签名很好奇。在我看来,声明应该是 module type SOURCE = sig class ctx : object inherit CouchDB.ctx end (* ... *) end;这不符合你的需求吗? - user593999
2个回答

5

[已过时...

最接近的方法是将签名定义为:

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

当然,你也可以直接写成以下形式:
module type SOURCE = sig
  type source
  val get : source -> #CouchDB.ctx -> string 
end

注意,OCaml 对象使用 结构化 类型。这意味着,即使您想要限制更多,也不能比上面所述的更加严格。它甚至没有限制 get 的参数必须是 CouchDB.ctx 或派生类的实例——任何具有相同方法(至少)的对象都将兼容。即使您编写:

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

您可以传递任何具有相同方法的对象。类型CouchDB.ctx只是特定结构对象类型的缩写,恰好与同名类生成的对象匹配。它不限于那些对象。为了确保:这被认为是一个功能特性。
======]
编辑2:通过扩展示例,我现在看到您想要什么和为什么。不幸的是,在OCaml中直接实现这一点是不可能的。您需要部分抽象类型。也就是说,您需要能够编写
module type SOURCE = sig
  type ctx < CouchDB.ctx
  ...
end

这在OCaml中是不可用的。但是,如果你愿意在签名中提供一个显式的向上转换,就可以接近实现:

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

然后,在Cache中,您需要将ctx#get的出现替换为(S.up ctx)#get,对于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)

请注意,我还将在SOURCE签名中透明地设置了type source = string。如果没有这个,我无法看出ctx#get source如何在Cache 函数中通过类型检查。

我不明白如何使第一个例子适应我的情况(我的上下文类没有参数)。第二个版本是不正确的,因为它会期望 Source.get 接受 CouchDB.ctx任何子类,而实际上它只接受一个Async.ctx)。 - Victor Nicollet
好的,为什么你想要限制Source.get呢?就我所看到的,这并不是必要的,也没有任何好处(即使你使用类名,所有子类型都是纯结构的OCaml)。我建议不要试图将某种名义子类型强加于OCaml上,因为这不是该语言的工作方式。OCaml更青睐于结构类型而非名义类型,并且优先考虑多态性而非子类型。只有在需要进行类型检查时才约束类型。 - Andreas Rossberg
该类型被限制为演示目的。在我的实际代码库中,该约束是从函数体中推断出来的,这显然比这里呈现的更复杂。我试图呈现我的问题,而不是从我的代码中复制数千行。由于使用类型约束进行演示目的很容易引起混淆,因此我已经编辑了上面的代码以获得额外的澄清。 - Victor Nicollet
啊,好的,现在我明白你在做什么了。请查看我的答案更新。 - Andreas Rossberg
我明白了。这是我曾经用作临时解决方案,但看起来它将成为永久性的解决方案。 - Victor Nicollet

1

除非我误解了你的意思,否则这应该可以解决问题:

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

class ctx : CouchDB.ctx 是一个class-specification。OCaml文档将它们描述为:

这是类定义签名的对应物。如果类规范具有相同的类型参数并且它们的类型匹配,则类规范与类定义匹配。

还有这个:

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

这两者略有不同。前者需要在模块中定义一个真正的类,而后者接受一个类定义或类类型定义(即类类型别名)。


我已经更新了我的问题,包括一个使用函数对象的示例,这样你也可以编译它。你的解决方案阻止了SomeSourceSOURCE签名匹配,因为它们不允许将ctx = Async.ctx作为类或类类型。 - Victor Nicollet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接