我正在将以下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 }
这让我想知道为什么会这样。为什么你不能像记录类型那样拥有记录类型的类型构造函数(如下所示)?
type ('q, 's) dfa = NFA of ('q * ('q->bool) * ( 'q -> 's -> 'q list) )
答案 0 :(得分:16)
为什么想要记录类型的类型构造函数,除非因为这是你在Haskell中的习惯?
在Haskell中,记录并不完全是一流的结构:它们更像是元组之上的语法糖。您可以定义记录字段名称,将它们用作访问者,并进行部分记录更新,但这可以通过普通元组中的位置进行访问。因此构造函数名称必须在desugaring之后告诉另一个记录:如果你没有构造函数名称,那么两个具有不同字段名但具有相同字段类型的记录将会变成等价类型,这将是一件坏事。
在OCaml中,记录是一种原始概念,它们有自己的身份。因此,它们不需要头构造函数来区分它们与元组或相同字段类型的记录。我不明白为什么你想添加一个头部构造函数,因为这更加冗长,没有提供更多信息或帮助表达性。
为什么不能像记录类型那样拥有记录类型的类型构造函数(如下所示)?
小心!您显示的示例中没有元组,只有具有多个参数的sum类型。 Foo of bar * baz
与Foo of (bar * baz)
不同:前一个构造函数有两个参数,后一个构造函数只有一个参数,即一个元组。这种区分是出于性能原因(在内存中,两个参数与构造函数标记一起打包,而元组创建一个间接指针)。使用元组而不是多参数稍微更灵活:您可以匹配Foo (x, y) -> ...
或Foo p -> ...
,后者不可用于多参数构造函数。
元组和记录之间没有不对称性,因为它们在sum类型构造中都没有特殊的状态,它只是任意arity构造函数的总和。也就是说,使用元组作为构造函数的参数类型更容易,因为不必声明使用元组类型。例如。有些人要求写能力
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中所做的那样)。在编程语言设计中,通常没有“好”和“坏”的选择,只有不同的妥协。答案 1 :(得分:2)
你不能拥有它,因为语言不支持它。它实际上很容易适应类型系统或数据表示,但它在编译器中只是一个小的额外复杂性,并且还没有完成。所以是的,你必须在命名构造函数和命名参数之间做出选择。
请注意,记录标签名称与特定类型相关联,因此例如{initialState=q}
是('q, 's) nfa
类型的模式;您无法有效地重用其他类型的标签名称。因此,当类型具有多个构造函数时,命名构造函数才真正有用;那么,如果你有很多构造函数的参数,你可能更喜欢为参数定义一个单独的类型。
我相信这个功能有一个补丁,但我不知道它是否是最新的OCaml版本或其他任何东西,它需要任何人使用你的代码来获得该补丁。