[英]What are row types? Are they algebraic data types?
我经常听说F#缺乏对OCaml行类型的支持,这使得语言比F#更强大。
这些是什么? 它们是代数数据类型,例如总和类型(有区别的联合)还是产品类型(元组,记录)? 是否可以在其他方言中写行类型,例如F#?
首先,我们需要修复术语。 至少在类型理论中,特别是在OCaml的类型系统中,没有“行类型” †这样的东西。 存在“行多态”,我们将在0以下讨论它。
行多态性是多态的一种形式。 OCaml提供两种多态 - 参数和行,而缺少其他两种 - ad hoc和包含(也称为子类型) 1 。
首先,什么是多态 ? 在类型系统的上下文中,多态性允许单个术语具有多种类型。 这里的问题是单词类型本身在计算机科学和编程语言社区中严重超载。 因此,为了最大限度地减少混淆,让我们在这里重新介绍它,在同一页2上 。 一种术语通常表示术语语义的某种近似。 语义可以像一组配有一组操作或更复杂的值(如效果,注释和任意理论)一样简单。 通常,语义表示一个术语的所有可能行为的集合。 类型系统‡表示一组规则,允许某些语言构造,并根据其类型禁止其他语言。 即,它验证术语的组合行为是否正确。 例如,如果某种语言中存在函数应用程序构造,则类型系统将仅允许应用程序使用具有与参数类型匹配的类型的参数。 这就是多态性发挥作用的地方。 在单形类型系统中,这种匹配可能只是一对一,即文字。 多态类型系统提供了指定一些与一族类型匹配的正则表达式的机制。 因此,不同类型的多态性只是不同类型的正则表达式,您可以使用它们来表示类型族。
现在让我们从这个角度来看待不同类型的多态。 例如,参数多态就像正则表达式中的点。 例如, 'a list
是. list
. list
- 表示我们按字面顺序与list
匹配, list
类型的参数可以是任何类型。 行多态性是一个星型运算符,例如<quacks : unit; ..>
<quacks : unit; ..>
和<quacks : unit; .*>
<quacks : unit; .*>
。 这意味着它与任何类型的quacks
和其他任何类型3相匹配。 说到名义上的子类型,在这种情况下,我们有名义类(在regexp中也称为字符类),并且我们指定了一系列具有其基类名称的类型。 例如, duck
就像[:duck:]*
,任何正确注册为类成员的值都与此类型匹配(通过类继承和new运算符) 4 。 最后,ad-hoc多态实际上也是名义上的,并映射到正则表达式中的字符类。 这里的主要区别在于ad-hoc多态的类型概念不是应用于值,而是应用于名称。 因此,名称(如函数名称或+
运算符)可能具有多个定义(实现),这些定义应该使用某种语言机制静态注册(例如,重载运算符,实现方法等)。 因此,ad-hoc多态只是名义子类型的特例。
现在,当我们清楚时,我们可以讨论行多态性给我们带来什么。 行多态性是结构类型系统的一个特征(也称为动态类型语言中的鸭子类型),与提供子类型多态性的名义类型系统形成对比。 一般来说,正如我们上面所讨论的,它允许我们指定一种类型为“任何嘎嘎叫”而不是“任何实现IDuck接口的东西”。 当然,你可以通过定义duck接口并使用一些inherit
或implements
机制显式地将所有实现注册为此接口的实例来对标称类型执行相同的操作。 但这里的主要问题是您的层次结构是密封的,即您需要更改代码以在新创建的接口中注册实现。 这打破了开放/封闭原则并阻碍了代码重用。 名义子类型的另一个问题是,除非你的层次结构形成一个格子(即,对于任何两个类总是有一个最小上限),你不能在它上面实现类型推断5 。
目标ML:ML的有效面向对象扩展 - 对该主题的全面描述。
FrançoisPottier和DidierRémy。 ML类型推理的本质。 在Benjamin C. Pierce,编辑,类型和编程语言高级主题,麻省理工学院出版社,2005年。 - 有关行的详细解释,请参见第10.8节。
结构多态性的简单类型推断 - 详细解释在存在类型推断时结构和行多态之间的相互作用。
0)正如@nekketsuuu在评论中指出的那样,我使用的术语有点自愿,因为我的目的是提供一个易于理解和高层次的想法,而不是深入细节。 从那以后我修改了帖子,使其更加严格。
1)然而,OCaml提供了具有继承和子类型概念的类,它仍然不是根据通用定义的子类型多态性,因为它不是名义上的。 从答案的其余部分可以更清楚地看出来。
2)我只是在修理术语,我并不是说我的定义是正确的。 许多人认为类型表示值的表示,并且历史上这是正确的。
3)也许更好的正则表达式是<.*; quacks : unit; .*>
<.*; quacks : unit; .*>
<.*; quacks : unit; .*>
但我认为你明白了。
4)因此OCaml没有子类型多态性,尽管它有一个子类型的概念。 当您指定一个与子类型不匹配的类型时,它只会按字面意思匹配,并且您需要使用显式向上转换运算符来使类型T的值适用于需要super(T)
的上下文中。 因此,尽管OCaml中存在子类型,但它与多态性无关。
5)虽然格子要求看起来不可能,但在现实生活中很难对层次结构施加这种限制,或者如果强加,则类型推断的精度将始终与类型层次结构的精度相关联。 所以在实践中,它不起作用,参见 斯卡拉
† (在第一次阅读时跳过此注释)虽然在OCaml中存在行变量 ,用于将行多态嵌入到仅具有参数多态的OCaml类型推断中。
‡)通常,单词输入与类型系统可互换使用,以指代整个类型系统中的一组特定规则。 例如,有时我们说“OCaml有行键入”来表示OCaml类型系统为“行多态”提供规则的事实。
行类型很奇怪。 而且非常强大。
行类型用于在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中,行类型有两个主要用途: 对象和多态变体 。 在代数方面,对象为您提供“行产品”和多态变体“行和”。
关于行类型的注意事项是,您最终可以使用一些子类型进行声明,并且非常反直观的类型和语义(特别是在案例类中)。
您可以查看本文以获取更多详细信息。
我将用他的例子完成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; .. >
< a : int; .. >
是一种字面意思是“任何至少a
带有签名int
的方法a
对象”的类型 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.