我正在尝试使用OCaml(3.12.1)的模块语言,为模块定义函子和签名,等等,主要是按照Chapter 2 of the OCaml manual的示例进行,并且偶然发现了我的心理模型,函数和模块签名的工作方式存在缺陷。我试图将遇到的情况缩小到尽可能短的代码,所以不要问我要完成什么,这是一个完全人为设计的示例,用于演示有问题的OCaml功能。
因此,我们有一个函子,该函子仅提供标识函数“ 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身份函数生成一个模块:
module IdentityInt = Identity(IntType) ;;
确保生成的模块及其f函数的行为符合预期:
#IdentityInt.f(3) + 10 ;;
- : int = 13
函子的心理模型是将模块作为输入模块和返回模块的函数,到目前为止似乎在为我们服务。
Identity
函子期望将签名(模块类型)SOMETYPE的模块作为输入参数,并且确实我们提供的模块(IntType
)具有正确的签名,因此将生成一个有效的输出模块(IdentityInt
),其函数的行为符合预期。现在是不直观的部分。如果我们想让提供的模块
f
确实是SOMETYPE类型的模块,该怎么办。如:module IntType : SOMETYPE = struct type t = int end ;;
然后以与以前相同的方式生成函子的输出模块:
module IdentityInt = Identity(IntType) ;;
...让我们尝试使用新生成的模块的
IntType
函数:IdentityInt.f 0 ;;
因此,REPL投诉:
"Error: This expression [the value 0] has type int but an expression was expected of type IntType.t."
提供冗余但正确的类型信息如何破坏代码?即使在A的情况下,函子模块Identity也必须将
f
模块视为IntType
类型。那么,如何显式地将SOMETYPE
声明为IntType
类型会产生不同的结果呢? 最佳答案
:
构造在核心语言和模块语言方面有所不同。在核心语言中,它是一个注释构造。例如,((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)