Haskell的代数数据类型
我试图完全理解Haskell的所有概念。
代数数据类型在哪些方面类似于通用类型(例如Cand Java)?它们又有什么不同?无论如何,它们的代数是什么?
我对通用代数及其环和域很熟悉,但是对Haskell的类型如何工作只有一个模糊的想法。
解决方案
回答
Haskell中的"代数数据类型"支持完整的参数多态性,这是泛型在技术上更正确的名称,作为简单的示例,列表数据类型为:
data List a = Cons a (List a) | Nil
等效于(尽可能,并忽略非严格评估等)
class List<a> { class Cons : List<a> { a head; List<a> tail; } class Nil : List<a> {} }
当然,Haskell的类型系统允许更多...有趣地使用类型参数,但这只是一个简单的示例。关于"代数类型"的名称,老实说,我从来没有完全确定将其命名的确切原因,但是我假设这是由于类型系统的数学基础所致。我认为原因可以归结为ADT是"一组构造函数的产品"的理论定义,但是距我大学毕业已经过去了两年,所以我不再记得具体细节了。
[编辑:感谢克里斯·康韦(Chris Conway)指出了我的愚蠢错误,ADT当然是求和类型,构造函数提供了字段的乘积/元组。
回答
对我而言,Haskell的代数数据类型的概念在C#等面向对象语言中始终看起来像多态。
请参阅http://en.wikipedia.org/wiki/Algebraic_data_types中的示例:
data Tree = Empty | Leaf Int | Node Tree Tree
这可以在Cas的TreeNode基类中实现,它具有派生的Leaf类和派生的TreeNodeWithChildren类,并且甚至需要派生的EmptyNode类。
(好的,我知道,没有人会这样做,但至少我们可以这样做。)
回答
Haskell的数据类型因其与分类初始代数的联系而被称为"代数"。但这就是疯狂。
@olliej:ADT实际上是"求和"类型。元组是产品。
回答
@Timbo:
我们基本上是对的,就像一个抽象的Tree类,带有三个派生类(Empty,Leaf和Node),但是我们还需要强制保证使用Tree类的某些人永远不会添加任何新的派生类,因为使用Tree数据类型的策略是编写基于树中每个元素的类型在运行时切换的代码(添加新的派生类型将破坏现有代码)。我们可以想象一下在Cor C ++中这种讨厌的情况,但是在Haskell,ML和OCaml中,这对于语言设计和语法至关重要,因此编码样式可以通过模式匹配以更加方便的方式支持它。
ADT(和类型)也类似于C或者C ++中的带标记的联合或者变量类型。
回答
这是一个古老的问题,但没有人提到可空性,这是代数数据类型的重要方面,也许是最重要的方面。由于每个值最有可能是替代值之一,因此基于穷举的基于案例的模式匹配是可能的。
回答
在通用代数中
代数由几组元素组成
(将每个集合视为一种类型的值的集合)
以及一些将元素映射到元素的操作。
例如,假设我们有一种"列表元素"和一个
"列表"的类型。作为操作,我们具有"空列表",它是一个0参数
函数返回一个"列表"和一个带有两个参数的" cons"函数,
一个"列表元素"和一个"列表",并产生一个"列表"。
此时,有许多适合描述的代数,
因为可能发生两种不良情况:
- 在"列表"集中可能存在一些无法从"空列表"和" cons操作"构建的元素,即所谓的"垃圾邮件"。这可能是从某个从天上掉下来的元素开始的列表,或者是没有开头的循环,或者是无限的列表。
- 应用于不同参数的"缺点"结果可能相等,例如将元素包含在非空列表中可能等于空列表。有时称为"混乱"。
不具有这些不良性质的代数称为
首字母缩写,这是抽象数据类型的预期含义。
名称的初始名称来自该属性,即确切存在
从初始代数到任何给定代数的同态
本质上,我们可以通过应用以下操作来评估列表的值
在另一个代数中,结果是明确的。
对于多态类型,它变得更加复杂...
回答
他们之所以称为代数的简单原因;有和(逻辑析取)和乘积(逻辑合取)两种类型。总和类型是有区别的联合,例如:
data Bool = False | True
产品类型是具有多个参数的类型:
data Pair a b = Pair a b
在O'Caml中,"产品"变得更加明确:
type 'a 'b pair = Pair of 'a * 'b