考虑以下代码
module type Foo = sig
type t
val do_with_t : t -> unit
end
let any_foo t (module F : Foo) = F.do_with_t t
以下可爱类型错误拒绝了:
Error: This expression has type F.t but an expression was expected of type F.t
The type constructor F.t would escape its scope
但是一旦我添加以下类型注释就被接受了:
let any_foo (type s) (t:s) (module F : Foo with type t = s) = F.do_with_t t
共享约束对我有意义所以我的问题是为什么OCaml不能通过我在函数内使用t
来推断类型签名。
答案 0 :(得分:7)
我不是专家,但这是我对此的看法。
真正的错误是消息的最后一位:
类型构造函数F.t将转义其范围
要理解错误消息,首先重写any_foo
,不要使用与参数匹配的模式,并重命名参数以使解释更容易理解:
let any_foo arg foo =
let (module F : Foo) = foo in
F.do_with_t arg
您正在使用第一类模块,并将变量foo
解压缩到该let语句范围内的新模块F
,。 /强>
现在让我们考虑可以从这个事实推断的参数arg
的类型。显然,类型为F.t
,但关键是这是一种仅在当前范围内已知的类型,因为module F
仅在当前范围内已知。
现在让我们尝试定义生成的any_foo
函数的类型:
val any_foo : F.t -> (module Foo) -> unit
还有你的问题,你试图在功能范围内深入揭示新的类型F.t
。换句话说,您希望调用者知道您的函数中仅存在的类型。或者,换句话说,您希望类型F.t
能够将其范围“转移”给更广泛的受众。
现在我们知道了这个问题,我们可以认识到需要向编译器解释这种类型存在于“外部”范围内,并且参数arg
属于该类型。
换句话说,我们需要向新创建的模块F
添加一个约束,以说明参数arg
的类型等于我们新模块中的类型t
F
。为此我们可以使用局部抽象类型。
继续使用相同的函数,我们可以添加一个本地抽象类型a
,并用它约束模块F
:
let (type a) any_foo arg foo =
let (module F : Foo with type t = a) = foo in
F.do_with_t arg
现在考虑any_foo
的类型。
val any_foo : 'a -> (module Foo with type t = 'a) -> unit
没有问题。
为了完整性,让我们回到我们的模式匹配版本:
let (type a) any_foo arg (module F : Foo with type t = a) =
F.do_with_t arg
答案 1 :(得分:5)
这并没有真正回答你的问题,但在你的情况下,你只需要引入一个新的类型变量s
:
let any_foo (type s) t (module F : Foo with type t = s) = F.do_with_t t
即。你不需要(t:s)
因为类型推断在这里可以正常工作。