Ocaml 没有记录类型的类型构造函数?

Ocaml 没有记录类型的类型构造函数?,ocaml,Ocaml,我正在将以下Haskell代码翻译为OCaml: data NFA q s = NFA { intialState :: q , isAccepting :: q -> Bool , transition :: q -> s -> [q] } 起初,我尝试了一种非常直译的翻译: type ('q,'s) nfa = NFA of { initialState: 'q; isAccepting: 'q -> bo

我正在将以下Haskell代码翻译为OCaml:

data NFA q s = NFA
{ intialState :: q
, isAccepting :: q -> Bool
, transition  :: q -> s -> [q]
}
起初,我尝试了一种非常直译的翻译:

type ('q,'s) nfa = NFA of { initialState: 'q;
                            isAccepting:  'q -> bool;
                            transition:   'q -> 's -> 'q list }
…当然,这会导致语法错误,因为不允许使用类型构造函数部分“NFA of”。它必须是:

 type ('q,'s) nfa = { initialState: 'q;
                      isAccepting:  'q -> bool;
                      transition:   'q -> 's -> 'q list }
这让我想知道为什么会这样。为什么不能像对待元组类型一样对待记录类型使用类型构造函数(如下所示)


您不能拥有它,因为该语言不支持它。实际上,它很容易适应类型系统或数据表示,但在编译器中,这将是一个小的额外复杂性,而且还没有完成。因此,是的,您必须在命名构造函数和命名参数之间进行选择

请注意,记录标签名称与特定类型相关,因此例如,
{initialState=q}
是类型
('q',s)nfa
的模式;无法在其他类型中有效地重用标签名称。因此,只有当类型有多个构造函数时,命名构造函数才真正有用;然后,如果一个构造函数有很多参数,那么您可能更愿意为这些参数定义一个单独的类型


我相信这项功能有一个补丁,但我不知道它是否是最新的OCaml版本或任何东西的最新版本,而且它需要任何使用您的代码的人都有这个补丁。

为什么您希望记录类型有一个类型构造函数,除非这是您在Haskell中的习惯

在Haskell中,记录并不完全是一级结构:它们更像是元组之上的语法糖。您可以定义记录字段名称,将其用作访问器,并执行部分记录更新,但这会通过普通元组中的位置来取消访问。因此,在desugaring之后,有必要使用构造函数名来区分一个记录和另一个记录:如果没有构造函数名,那么两个字段名不同但字段类型相同的记录将被desugar为等效类型,这将是一件坏事

在OCaml中,记录是一个原始概念,它们有自己的标识。因此,它们不需要头构造函数来将它们与元组或相同字段类型的记录区分开来。我不明白你为什么要添加一个head构造函数,因为这更冗长,没有提供更多信息或帮助表达

为什么不能像对待元组类型一样对待记录类型使用类型构造函数(如下所示)


小心!在您展示的示例中没有元组,只有一个具有多个参数的sum类型Foo与(bar*baz)的
Foo不同:前者有两个参数,后者只有一个参数,即元组。这种差异是出于性能原因(在内存中,两个参数与构造函数标记打包在一起,而元组创建一个间接指针)。使用元组而不是多参数稍微灵活一些:您可以同时匹配
Foo(x,y)->……
foop->……
,后者不适用于多参数构造函数

元组和记录之间没有不对称性,因为它们在和类型构造中都没有特殊的地位,而和类型构造只是任意算术的构造函数的和。也就是说,使用元组作为构造函数的参数类型更容易,因为元组类型不必声明就可以使用。有些人要求有写作能力

type foo =
  | Foo of { x : int; y : int }
  | Bar of { z : foo list }
而不是现在

type foo = Foo of foo_t | Bar of bar_t
and foo_t = { x : int; y : int }
and bar_t = { z : foo list }
您的请求是这个问题的一个特殊(但不是很有趣)案例。然而,即使使用这种速记语法,构造函数和数据之间仍然会有一个间接指针,这使得这种风格对注重性能的程序不具吸引力——有用的是能够命名构造函数参数


PS:我并不是说Haskell选择将记录分解成元组是件坏事。通过将一个功能转换为另一个功能,可以减少概念的冗余/重叠。也就是说,我个人认为将元组分解为记录(使用数字字段名,如Oz中所做的那样)更为自然。在编程语言设计中,通常没有“好的”和“坏的”选择,只有不同的折衷。

“为什么您想要记录类型的类型构造函数”-您是对的,它们通常没有用处,除了一种情况:包含记录的和类型:type somerecs=REC1 of{a:..;b:}REC2 of{c:..;d}。。。但正如你所说,这很容易解决。”在你展示的示例中没有元组,只有一个具有多个参数的求和类型。bar*baz的Foo与(bar*baz)的Foo不同)“谢谢,这是一个错误!几件事:1。当你说记录是一个“原始概念”和“有自己的身份”时,我想你的意思是,记录在OCaml中是一个特例,而在SML中,它们是第一类的。2.程序员(有理由)不知道内存布局细节,从程序员的角度来看,记录和元组w.r.t构造函数参数之间存在不对称。你的例子说明了这一点。3.使用记录作为参数的构造函数与使用命名参数的构造函数不同。我认为声称记录在SML中是“第一类”而在OCaml中是“特例”是没有意义的。OCaml记录是名义上的(因此它们只作为类型定义出现),SML记录是结构化的(因此它们可以出现在类型表达式中的任何位置),这两种记录都是光谱的两端,它们都有众所周知的优点和不便之处(例如,SML记录声明在不通过变量定义来打破等递归循环的情况下是不能递归的,因此在这个设置中,“给它们一个构造函数”是有意义的)。
type foo = Foo of foo_t | Bar of bar_t
and foo_t = { x : int; y : int }
and bar_t = { z : foo list }