为什么我们需要“代数数据类型”?

时间:2015-08-01 08:09:12

标签: scala haskell algebraic-data-types

我已经阅读了代数数据类型的一些解释:

这些文章提供了非常详细的描述和代码示例。

起初我认为代数数据类型只是用于轻松定义某些类型,我们可以将它们与模式匹配相匹配。但在阅读这些文章之后,我发现那里甚至没有提到“模式匹配”,内容看起来很有趣,但比我预期的要复杂得多。

所以我有一些问题(这些文章没有回答):

  • 为什么我们在Haskell或Scala中需要它?
  • 如果我们拥有它,我们可以做什么,如果我们没有它,我们不能做什么?

2 个答案:

答案 0 :(得分:9)

我们应该从Haskell wiki文章Algebraic Data Types

开始

在这里,不久,只是我的愿景:

  • 我们需要它们以旧的面向对象的方式(或实际上以旧的基于类别的方式)对业务逻辑进行建模,并使其更加类型安全,因为编译器可以检查您是否匹配了所有可能的选择。或者,换句话说,您的函数是total,而不是部分。最终,它使编译器能够证明代码的正确性(这就是为什么建议使用密封特性)。所以,你拥有的类型越多越好 - 顺便说一句,通用编程可以帮助你,因为它可以产生更多的类型。
  • 标准功能:我们可以将类型表示为对象的“集合”,我们可以将对象与类型匹配(使用模式匹配),甚至可以使用匹配器对其进行解构(分析)。我们还可以使用类型classess动态地将行为(在编译时)添加到此类型。对于常规类也是可能的,但是这里它使我们能够将代数模型(类型)与行为(函数)分开。
  • 我们可以将类型构造为不同对象/类型的products / coproducts。您可以将代数类型系统视为一组(或更一般地 - 作为笛卡尔封闭类别)。 type Boolean = True | False表示布尔值是TrueFalse的联合(联合产品)。 Cat(height: Height, weight: Weight)HeightWeight的“元组”(更常见的 - 产品)。产品(更多)代表OOP的“部分”关系,产品 - “是”(但是相反)。

它还为我们提供了一种以多方式方式在运行时调度行为的方法(就像在CLOS中一样):

  sealed trait Animal
  case class Cat extends Animal
  case class Dog extends Animal

 def move(c: Animal) = c match {
   case Cat(...) => ...
   case Dog(...) =>
   case a: Animal => ...//if you need default implementation
 }

的Haskell样:

 data Animal = Dog | Cat //coproduct

 let move Dog d = ...
 let move Cat c = ...

而不是:

trait Animal {
  ...
  def move = ...
}

class Cat(val ...) extends Animal {
  override def move = ...
}

class Dog(val ...) extends Animal {
  override def move = ...
}

P.S。从理论上讲,如果你用代数方式对世界进行建模,并且你的功能是完全和纯粹的 - 你可以证明你的应用是正确的。如果它编译 - 它的工作原理:)。

P.S.2。我还应该提一下,Scala的类型层次结构中包含Any并不适合类型安全(但对于Java和IO的互操作很好),因为它打破了GADT定义的漂亮结构。超过case class可能同时是GADT(代数)和ADT(抽象),这也减少了保证。

答案 1 :(得分:2)

你提到的博客文章更多的是关于代数数据类型的数学方面,它们在编程中的实际应用。我认为大多数函数式程序员首先在一些编程语言中使用它们来学习代数数据类型,然后才研究它们的代数性质。

事实上,博客文章的意图从一开始就是明确的:

  

在这一系列帖子中,我将解释为什么Haskell的数据类型   称代数 - 不提类别理论或先进   数学。

无论如何,代数类型的实用性最好通过玩它们来理解。

例如,假设您想编写一个函数来交叉平面上的两个段。

def intersect(s1: Segment, s2: Segment): ???

结果应该是什么?写作很有诱惑力

def intersect(s1: Segment, s2: Segment): Point

但如果没有交集怎么办?有人可能会尝试通过返回null或抛出NoIntersection例外来修补该案例。但是,当它们位于同一直线上时,两个段也可能在多个点上重叠。在这种情况下我们该怎么做?抛出另一个例外?

代数类型方法是设计一种涵盖所有情况的类型:

sealed trait Intersection
case object NoIntersection extends Intersection
case class SinglePoint(p: Point) extends Intersection
case class SegmentPortion(s: Segment) extends Intersection

def intersect(s1: Segment, s2: Segment): Intersection

在许多实际案例中,这种方法感觉很自然。在其他一些缺乏代数类型的语言中,人们必须求助于null s(也见the billion-dollar mistake)的异常,以及非密封类(使编译器无法检查模式的详尽性)匹配),或该语言提供的其他功能。可以说,最好的" OOP中的选项是使用Visitor pattern来编码没有这些特征的语言中的代数类型和模式匹配。尽管如此,在scala中直接支持该语言会更加方便。