Haskell的代数数据类型

时间:2008-08-19 19:18:42

标签: data-structures haskell types functional-programming algebraic-data-types

我正在努力完全理解Haskell的所有概念。

代数数据类型在哪些方面类似于泛型类型,例如在C#和Java中?它们有何不同?无论如何,他们有什么代数呢?

我熟悉通用代数及其环和字段,但我对Haskell的类型如何工作只有一个模糊的概念。

8 个答案:

答案 0 :(得分:100)

Haskell的代数数据类型被命名为,因为它们对应于类别理论中的初始代数,给我们一些定律,一些操作和一些操作符号。我们甚至可以使用代数表示法来描述常规数据结构,其中:

  • +代表和类型(不相交的联合,例如Either)。
  • 代表产品类型(例如结构或元组)
  • X表示单身人士类型(例如data X a = X a
  • 1代表单位类型()
  • μ 用于最不固定的点(例如递归类型),通常是隐式的。

附加一些表示法:

    {li> X•X

事实上,你可能会说(在Brent Yorgey之后)Haskell数据类型是常规的,如果它可以用1X+,{{1最简单的点。

使用这种表示法,我们可以简明地描述许多常规数据结构:

  • 单位:

    data () = ()

  • 选项:1

    data Maybe a = Nothing | Just a

  • 列表:1 + X

    data [a] = [] | a : [a]

  • 二叉树:L = 1+X•L

    data BTree a = Empty | Node a (BTree a) (BTree a)

其他操作(取自Brent Yorgey的论文,参考文献中列出):

  • 扩展:展开修复点有助于思考列表。 B = 1 + X•B²(即列表为空,或者它们有一个元素,或两个元素,或三个,或......)

  • 组合,L = 1 + X + X² + X³ + ...,给定类型F,组合G是构建“由G结构构成的F结构的类型“(例如F ◦ G,其中R = X • (L ◦ R)是列表,是玫瑰树。

  • 区分,数据类型D的导数(给定为D')是具有单个“洞”的D结构的类型,即,不包含任何数据的区别位置。这令人惊讶地满足与微积分差异相同的规则:

    L

    1′ = 0

    X′ = 1

    (F + G)′ = F' + G′

    (F • G)′ = F • G′ + F′ • G


参考文献:

答案 1 :(得分:22)

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当然是总和类型,提供产品/字段元组的构造函数]

答案 2 :(得分:20)

universal algebra 代数由一些元素组成 (将每个集合视为一个类型的值集合) 以及一些将元素映射到元素的操作。

例如,假设您有一种“列表元素”和一个 “列表”的类型。作为操作,您有“空列表”,这是一个0参数 函数返回一个“列表”和一个带有两个参数的“cons”函数, “列表元素”和“列表”,并生成“列表”。

此时有许多符合描述的代数, 因为可能会发生两件不愉快的事情:

  • “list”中可能存在无法构建的元素 从“空名单”和“利弊行动”,即所谓的“垃圾”。 这可能是从一些从天上掉下来的元素开始的列表, 或没有开头或无限列表的循环。

  • 适用于不同论点的“缺点”结果可能相同, 例如将元素提供给非空列表 可能等于空列表。这有时被称为“混乱”。

调用既没有这些不良属性的代数 initial ,这是抽象数据类型的预期含义。

名称initial来源于确切存在的属性 从初始代数到任何给定代数的一个同态。 基本上,您可以通过应用操作来评估列表的值 在另一个代数中,结果是明确定义的。

多态类型变得更加复杂......

答案 3 :(得分:12)

它们被称为代数的一个简单原因;有sum(逻辑析取)和product(逻辑合取)类型。和类型是一种有区别的联合,例如:

data Bool = False | True

产品类型是具有多个参数的类型:

data Pair a b = Pair a b

在O'Caml中,“产品”更明确:

type 'a 'b pair = Pair of 'a * 'b

答案 4 :(得分:8)

Haskell的数据类型称为“代数”,因为它们与categorical initial algebras的连接。但那种方式就是疯狂。

@olliej:ADT实际上是“和”类型。元组是产品。

答案 5 :(得分:3)

@Timbo:

你基本上认为它有点像一个带有三个派生类(Empty,Leaf和Node)的抽象Tree类,但是你还需要强制保证使用你的Tree类的某个人永远不会添加任何类。新的派生类,因为使用Tree数据类型的策略是编写在运行时根据树中每个元素的类型切换的代码(并且添加新的派生类型会破坏现有代码)。您可以想象这在C#或C ++中变得讨厌,但在Haskell,ML和OCaml中,这是语言设计和语法的核心,因此编码风格通过模式匹配以更方便的方式支持它。

ADT(和类型)在C或C ++中也类似于tagged unionsvariant types

答案 6 :(得分:2)

老问题,但没有人提到可空性,这是代数数据类型的一个重要方面,也许是最重要的方面。由于每个值大多数是备选方案之一,因此可以进行详尽的基于案例的模式匹配。

答案 7 :(得分:0)

对我来说,Haskell的代数数据类型的概念在C#等OO语言中总是看起来像多态。

查看http://en.wikipedia.org/wiki/Algebraic_data_types中的示例:

data Tree = Empty 
          | Leaf Int 
          | Node Tree Tree

这可以在C#中作为TreeNode基类实现,具有派生的Leaf类和派生的TreeNodeWithChildren类,如果你想要一个派生的EmptyNode类。

(好吧,我知道,没有人会这样做,但至少你可以做到。)