我经常听说 F# 不支持 OCaml 行类型,这使得 OCaml 比 F# 更加强大。
它们是什么?它们是代数数据类型,例如和类型(discriminated unions)或积类型(元组,记录)吗?在其他方言中,比如 F# 中是否可以编写行类型?
我经常听说 F# 不支持 OCaml 行类型,这使得 OCaml 比 F# 更加强大。
它们是什么?它们是代数数据类型,例如和类型(discriminated unions)或积类型(元组,记录)吗?在其他方言中,比如 F# 中是否可以编写行类型?
'a list
是 . list
- 这意味着我们与 list
字面匹配,而 list
类型的参数可以是任何类型。行多态性是一个星号操作符,例如,<quacks : unit; ..>
与 <quacks : unit; .*>
是相同的。它意味着它与任何具有 quacks
属性的类型匹配,并执行其他操作。谈到名义子类型,这种情况下我们有名义类(也称为正则表达式中的字符类),并且我们使用它们的基类的名称来指定一组类型。例如,duck
就像 [:duck:]
,任何被正确“注册”为该类成员的值都与该类型匹配(通过类继承和 new 操作符)。最后,特设多态性实际上也是名义的,并且映射到正则表达式中的字符类。这里的主要区别在于特设多态性中的类型概念不是应用于值,而是应用于名称。因此,一个名称,比如函数名或者 +
运算符,可能有多个定义(实现),这些定义应该使用某种语言机制进行静态注册(例如,重载运算符,实现方法等)。因此,特设多态性只是名义子类型的一种特殊情况。inherit
或implements
机制。但是这里的主要问题是你的继承关系是封闭的,也就是说,你需要改变你的代码来在新创建的接口中注册一个实现。这违反了开闭原则并阻碍了代码的重用。名义子类型的另一个问题是,除非你的继承关系形成一个格子(即对于任意两个类,总是存在一个最小上界),否则你无法对其进行类型推断5。
Objective ML:一种有效的面向对象的ML扩展 - 对该主题的全面描述。
François Pottier和Didier Rémy。ML类型推断的本质。在Benjamin C. Pierce(编辑),《类型和编程语言的高级主题》,MIT出版社,2005年。 - 请参阅第10.8节,了解关于行的非常彻底和详细的解释。
结构多态的简单类型推断 - 对在类型推断存在的情况下,结构和行多态之间相互作用的详细解释。
---- 0)正如@nekketsuuu在评论中指出的那样,我在使用术语时有些主观,因为我的意图是给出一个易于理解和高层次的概念,而不深入细节。自那时以来,我已经修订了帖子,使其稍微更加严格。
1) 尽管OCaml提供了具有继承和子类型概念的类,但根据通常的定义,它并不是一种子类型多态,因为它不是名义上的。从回答的其余部分可以更清楚地看出这一点。
2) 我只是在修正术语,我并不声称我的定义是正确的。许多人认为类型表示值的表示,从历史上看这是正确的。
3) 也许更好的正则表达式应该是<.*; quacks : unit; .*>
,但我认为你已经理解了意思。
4) 因此,尽管OCaml具有子类型的概念,但它没有子类型多态。当您指定一个类型时,它不会与子类型匹配,它只会字面匹配,并且您需要使用显式的向上转型运算符将类型为T的值应用于期望super(T)
的上下文中。因此,尽管OCaml中存在子类型,但它与多态无关。
5) 虽然格子要求看起来并不难,但在现实生活中,对层次结构施加这种限制很难,或者如果施加了这种限制,类型推断的精确性将始终与类型层次的精确性绑定在一起。所以在实践中,它行不通,参见Scala。
†(第一次阅读时请跳过此注释)尽管在OCaml中存在用于将行多态嵌入OCaml类型推断的行变量,但OCaml类型推断仅具有参数多态。
‡) 通常,单词typing与类型系统可以互换使用,用于指代整体类型系统中的一组特定规则。例如,有时我们说"OCaml具有行类型"来表示OCaml类型系统提供了"行多态"的规则。
defgeneric
)。因此,当我们定义void f(int x) {...}
时,我们隐含地定义了一个接口f(x)
,这使得所有实现方法f
的值,例如void f(float x) {...}
成为函数f
作为参数接受的类型族的一部分。顺便说一句,CLOS是一个很好的例子,其中名义子类型实际上是通过重载实现的。 - ivg行类型很奇怪,但非常强大。
行类型用于在OCaml中实现对象和多态变体。
但首先,这里是没有行类型情况下我们无法做到的事情:
type t1 = { a : int; b : string; }
type t2 = { a : int; c : bool; }
let print_a x = print_int x.a
let ab = { a = 42; b = "foo"; }
let ac = { a = 123; c = false; }
let () =
print_a ab;
print_a ac
当然,这段代码不会编译,因为print_a
必须具有唯一类型:要么是t1
,要么是t2
,但不能同时具有。然而,在某些情况下,我们可能希望出现这种行为。这就是行类型的作用。它们的作用是提供更加“灵活”的类型。
在OCaml中,行类型有两个主要用途:对象和多态变体。从代数角度来看,对象给你“行乘积”,而多态变体则给你“行总和”。
需要注意的是,行类型可能导致一些子类型声明,并且在类型和语义上非常反直觉(特别是在类的情况下)。有关详细信息,请参见本文。
module F(A : sig type t end)
即可。此外,对象、类和多态变量并非特别的。到目前为止,在OCaml中还没有特别的多态性,直到添加模块隐式参数。 - ivg我将使用类对PatJ的优秀答案进行补充,他的例子已经用类写出。
给定以下类:
class t1 = object
method a = 42
method b = "Hello world"
end
class t2 = object
method a = 1337
method b = false
end
以下是对象:
let o1 = new t1
let o2 = new t2
let print_a t = print_int t#a;;
val print_a : < a : int; .. > -> unit = <fun>
print_a o1;;
42
- : unit = ()
print_a o2;;
1337
- : unit = ()
print_a
的签名中看到行类型。 < a : int; .. >
是一种类型,字面上意思是"具有至少一个带有签名int
的方法a
的任何对象"。(<标签:字符串;..> as 'a)->'a
- ivg
private
关键字在行类型情况下的行为描述,而不是关于行类型本身的描述。 - PatJ