无法在递归函数中推断出此打包模块的签名

The signature for this packaged module couldn't be inferred in recursive function

我仍在尝试弄清楚在使用 Mirage 时如何拆分代码,它是第一个 class 模块的无数个。 我把我需要的一切都放在一个丑陋的大 Context 模块中,以避免必须将十个模块传递给我的所有功能,一个就够痛苦的了。

我有一个通过 tcp 接收命令的功能:

let recvCmds (type a) (module Ctx : Context with type chan = a) nodeid chan = ...

经过数小时的反复试验,我发现我需要添加 (type a) 和 "explicit" type chan = a 才能使其正常工作。看起来很丑,但它可以编译。 但是如果我想让那个函数递归:

let rec recvCmds (type a) (module Ctx : Context with type chan = a) nodeid chan =
  Ctx.readMsg chan >>= fun res ->
  ... more stuff ...
  |> OtherModule.getStorageForId (module Ctx)
  ... more stuff ...
  recvCmds (module Ctx) nodeid chan

我通过了两次模块,第一次没问题但是 我在递归行上收到错误:

The signature for this packaged module couldn't be inferred.

如果我尝试指定签名,我会得到

This expression has type a but an expression was expected of type 'a
The type constructor a would escape its scope

而且我似乎无法使用整个 (type chan = a) 东西。 如果有人可以解释发生了什么,最好是解决它的方法,那就太好了。 我当然可以只用一会儿,但我宁愿最终理解这些该死的模块。谢谢!

实用的答案是递归函数应该用 let rec f: type a. .... = fun ... 普遍量化它们的局部抽象类型。

更准确地说,您的示例可以简化为

module type T = sig type t end 
let rec f (type a) (m: (module T with type t = a)) = f m

产生与您相同的错误:

Error: This expression has type (module T with type t = a) but an expression was expected of type 'a The type constructor a would escape its scope

这个错误可以通过显式的 forall 量化来修复:这可以通过 速记符号(用于通用量化的局部抽象类型):

let rec f: type a.  (module T with type t = a) -> 'never = fun m -> f m

此行为背后的原因是局部抽象类型不应转义 引入它们的函数的范围。例如,这段代码

let ext_store = ref None
let store x = ext_store := Some x
let f (type a) (x:a) = store x

应该明显失败,因为它试图存储类型 a 的值,这是 f 主体之外的无意义类型。

因此,具有局部抽象类型的值只能由多态函数使用。比如这个例子

  let id x = x
  let f (x:a) : a = id x

很好,因为 id x 适用于任何 x.

这样的函数的问题
 let rec f (type a) (m: (module T with type t = a)) = f m

那么 f 的类型还没有在它的体内泛化,因为 ML 中的类型泛化发生在 let 定义处。因此,解决方法是明确告诉编译器 f 在其参数中是多态的:

 let rec f: 'a. (module T with type t = 'a) -> 'never =
   fun (type a) (m:(module T with type t = a)) -> f m

这里,'a. ... 是一个通用量化,应该读作 forall 'a. ...。 第一行告诉编译器函数 f 在它的第一个参数中是多态的,而第二行显式地引入局部抽象类型 a 来改进打包模块类型。拆分这两个声明非常冗长,因此 shorthand 符号结合了两者:

let rec f: type a.  (module T with type t = a) -> 'never = fun m -> f m