我正在尝试使用OCaml的模块语言(3.12.1),为模块定义仿函数和签名等等,主要是遵循Chapter 2 of the OCaml manual的示例,我偶然发现了一个情况显然我的仿函数和模块签名如何工作的心理模型是有缺陷的。我试图将我遇到的情况缩小到可能的最短代码量,所以不要问我想要完成什么,这是一个完全人为的例子来演示有问题的OCaml功能。
因此,我们有一个functor,它只提供一个标识函数'f',并由一个提供该函数输入参数类型的模块进行参数化。像我说的完全做作的例子。
module type SOMETYPE = sig type t end ;;
module Identity = functor (Type: SOMETYPE) -> struct let f (x: Type.t) = x end ;;
鉴于上述情况,我们继续定义一个模块来提供int类型:
module IntType = struct type t = int end ;;
..然后我们使用仿函数为int identity函数生成一个模块:
module IdentityInt = Identity(IntType) ;;
生成的模块及其f函数确实按预期运行:
#IdentityInt.f(3) + 10 ;;
- : int = 13
仿函数的心智模型是将模块作为输入和返回模块的函数,它们似乎正在为我们服务。 Identity
仿函数期望输入参数为签名模块(模块类型)SOMETYPE,实际上我们提供的模块(IntType
)具有正确的签名,因此生成了有效的输出模块({{1其IdentityInt
函数的行为符合预期。
现在出现了非直观的部分。如果我们想明确提供的模块f
确实是SOMETYPE类型的模块,该怎么办?如:
IntType
然后以与以前相同的方式生成仿函数的输出模块:
module IntType : SOMETYPE = struct type t = int end ;;
...让我们尝试使用新生成的模块的module IdentityInt = Identity(IntType) ;;
函数:
f
然后REPL抱怨:
IdentityInt.f 0 ;;
如何提供冗余但正确的类型信息会破坏代码?即使在案例A中,仿函数模块Identity也必须将"Error: This expression [the value 0] has type int but an expression was expected of type IntType.t."
模块视为IntType
类型。那么如何明确声明SOMETYPE
为IntType
类型会产生不同的结果呢?
答案 0 :(得分:10)
:
构造在核心语言和模块语言中是不同的。在核心语言中,它是一个注释构造。例如,((3, x) : 'a * 'a list)
将表达式约束为某种类型为'a * 'a list
的实例;由于该对的第一个元素是整数,let (a, b) = ((3, x) : 'a * 'a list) in a + 1
是良好类型的。模块上的:
构造并不意味着这一点。
构造M : S
将模块M
密封到签名S
。这是一个不透明的印章:在键入S
的使用时,只有签名M : S
中提供的信息仍然可用。当您编写module IntType : SOMETYPE = struct type t end
时,这是
module IntType = (struct type t end : SOMETYPE)
由于t
中的类型字段SOMETYPE
未指定,IntType
具有抽象类型字段t
:类型IntType
是新类型,由此定义生成。
顺便说一下,你可能意味着module IntType = (struct type t = int end : SOMETYPE)
;但无论哪种方式,IntType.t
都是抽象类型。
如果要在保留某些类型的情况下指定模块具有特定签名,则需要为这些类型添加显式相等性。没有构造可以添加所有可推断的等式,因为将签名应用于模块通常用于信息隐藏。为简单起见,该语言仅提供这种生成密封构造。如果要使用已定义的签名SOMETYPE
并保留类型t
的透明度,请在签名中添加约束:
module IntType = (struct type t = int end : SOMETYPE with type t = int)
答案 1 :(得分:7)
如果您在没有明确写出内容时查看推断的签名:
# module IntType = struct type t = int end ;;
module IntType : sig type t = int end
签名公开t
是int
。你的签名恰恰相反:
# module IntType : SOMETYPE = struct type t = int end ;;
module IntType : SOMETYPE
真的是:
# module IntType : sig type t end = struct type t = int end ;;
module IntType : sig type t end
这似乎可以解决您的问题:
# module IntType : (SOMETYPE with type t = int) = struct type t = int end ;;
module IntType : sig type t = int end
# module IdentityInt = Identity(IntType) ;;
module IdentityInt : sig val f : IntType.t -> IntType.t end
# IdentityInt.f 0 ;;
- : IntType.t = 0
(你不需要parens,但他们帮助精神上解析)。基本上,你揭露了t是带有签名的int的事实。因此OCaml知道相等IntType.t = int。
有关内幕的详细信息,我会留给更多知识渊博的人。
答案 2 :(得分:5)
当你写:
module IntType : SOMETYPE = struct type t = int end ;;
您将InType
的签名限制为SOMETYPE
。这意味着,例如,类型t
现在变成了抽象类型(其实现对于typer来说是未知的)。
所以IdentityInt.f
类型仍为IntType.t -> IntType.t
,但是,通过使用签名约束,您已明确从typer知识中删除了等式IntType.t = int
。你得到的错误信息正好告诉你。
答案 3 :(得分:5)
您的主要错误在于:
模块IntType:SOMETYPE = struct type t end ;;
当您归因于签名SOMETYPE
时,它是一个不透明的归属,并且与int
的身份将丢失。类型IntType.t
现在是抽象类型。
您需要归档签名SOMETYPE with type t = int
。
本抄本显示了不同之处:
# module type SOMETYPE = sig type t end;;
module type SOMETYPE = sig type t end
# module IntType : SOMETYPE with type t = int = struct type t = int end;;
module IntType : sig type t = int end
# module AbsType : SOMETYPE = struct type t = int end;;
module AbsType : SOMETYPE
模块和归属的语言设计问题在首席设计师的1994 paper on modules, types, and separate compilation中得到了很好的体现。毛茸茸的数学部分都可以跳过。