我仍在尝试找出使用Mirage时如何拆分代码的方法,这是无数一流的模块。
我已将我需要的所有内容放入一个丑陋的Context
模块中,以避免必须将十个模块传递给我的所有功能,其中一个已经足够痛苦。
我有一个通过tcp接收命令的功能:
let recvCmds (type a) (module Ctx : Context with type chan = a) nodeid chan = ...
经过数小时的尝试和错误,我发现需要添加(type a)
和“显式” 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)
东西。
如果有人可以解释正在发生的事情,并且是解决该问题的理想方法,那就太好了。
我当然可以花一会儿时间,但是我宁愿最终了解这些该死的模块。谢谢!
答案 0 :(得分:2)
实用的答案是递归函数应该使用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
产生与您相同的错误:
错误:此表达式具有类型(类型为t的模块T = a) 但是应该使用'a类型的表达式 类型构造函数a会逃避其作用域
可以通过显式的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
来优化打包的模块类型。拆分这两个声明非常冗长,因此速记符号将两者结合在一起:
let rec f: type a. (module T with type t = a) -> 'never = fun m -> f m