代码之家  ›  专栏  ›  技术社区  ›  Zifre

为什么haskell代数数据类型是“closed”?

  •  57
  • Zifre  · 技术社区  · 15 年前

    如果我错了,请纠正我的错误,但是Haskell中的代数数据类型似乎在许多情况下都很有用,在这些情况下,您将使用OO语言中的类和继承。但是有一个很大的区别:一旦声明了代数数据类型,它就不能扩展到其他地方。它是“关闭”的。在OO中,可以扩展已经定义的类。例如:

    data Maybe a = Nothing | Just a
    

    以后我无法在不修改此声明的情况下向此类型添加其他选项。那么这个系统有什么好处呢?看起来OO方式的可扩展性要大得多。

    8 回复  |  直到 9 年前
        1
  •  67
  •   Tom Lokhorst    15 年前

    ADT是关闭的,这使得编写总函数变得容易得多。这些函数总是为其类型的所有可能值生成一个结果,例如。

    maybeToList :: Maybe a -> [a]
    maybeToList Nothing  = []
    maybeToList (Just x) = [x]
    

    如果 Maybe 打开后,有人可以添加一个额外的构造函数, maybeToList 功能会突然中断。

    在OO中,这不是一个问题,当您使用继承来扩展一个类型时,因为当您调用一个没有特定重载的函数时,它只能使用超类的实现。也就是说,你可以打电话 printPerson(Person p) 很好,用 Student 对象如果 学生 是的子类 Person .

    在Haskell中,当需要扩展类型时,通常使用封装和类型类。例如:

    class Eq a where
       (==) :: a -> a -> Bool
    
    instance Eq Bool where
      False == False = True
      False == True  = False
      True  == False = False
      True  == True  = True
    
    instance Eq a => Eq [a] where
      []     == []     = True
      (x:xs) == (y:ys) = x == y && xs == ys
      _      == _      = False
    

    现在, == 函数是完全打开的,您可以通过使其成为 Eq 班级。


    注意,有人在研究 extensible datatypes 但这绝对不是哈斯克尔的一部分。

        2
  •  79
  •   kiritsuku    12 年前

    答案与代码易于扩展的方式有关,这是类和代数数据类型之间的紧张关系,菲尔·韦德勒称之为“表达式问题”:

    • 对于代数数据类型,

      • 它非常 便宜的 添加新的 对事物的操作 :您只需定义一个新函数。那些东西上的所有旧功能继续保持不变。

      • 它非常 昂贵的 添加新的 样东西 :必须向现有数据类型添加新的构造函数,并且必须 编辑并重新编译使用该类型的每个函数 .

    • 随班就读,

      • 它非常 便宜的 添加新的 样东西 :只需添加一个新的子类,并根据需要在该类中为所有现有操作定义专门的方法。超类和所有其他子类继续保持不变。

      • 它非常 昂贵的 添加新的 对事物的操作 你必须 向超类添加新的方法声明 和潜在的 向每个现有子类添加方法定义 . 在实践中,负担随方法的不同而变化。

    因此,代数数据类型是闭合的,因为闭合类型很好地支持某些类型的程序演化。例如,如果您的数据类型定义了一种语言,那么在不使旧的编译器无效或更改数据的情况下,很容易添加新的编译器过程。

    可能有“打开”的数据类型,但除非在仔细控制的情况下,类型检查变得困难。 托德·米尔斯坦做了一些 very beautiful work 在支持开放式代数类型和可扩展函数的语言设计中,所有这些都使用模块化类型检查器。我发现他的论文读起来很高兴。

        3
  •  15
  •   Dave    15 年前

    如果你写一个函数

    maybeToList Nothing = []
    maybeToList (Just x) = [x]
    

    然后你知道它永远不会产生运行时错误,因为你已经涵盖了所有的情况。只要类型是可扩展的,这就不再是真的了。在那些需要可扩展类型的情况下(它们比您想象的要少),规范的haskell解决方案是使用一个类型类。

        4
  •  11
  •   Don Stewart    15 年前

    选中“打开数据类型和打开函数” http://lambda-the-ultimate.org/node/1453

    在面向对象语言中,它是 通过定义新的 类,但很难添加 新功能。功能性 语言,情况是相反的: 添加新函数不构成 问题,但 扩展数据(添加 新的数据构造函数)需要 修改现有代码 . 问题 支持两个方向 可扩展性被称为 这个 表达式问题 . 我们提出开放 数据类型和打开函数 轻量级的表达式解决方案 哈斯克尔语言的问题。这个 其思想是开放数据的构造函数 开放函数的类型和方程 可以出现分散在 程序。特别是,他们可以 驻留在不同的模块中。这个 预期语义如下: 程序的行为应该像数据一样 类型和功能已关闭, 在一个地方定义。秩序 函数方程由 最佳匹配模式,其中 特定模式优先于 不具体的。我们展示了我们的 解决方案适用于 表达式问题,泛型 编程和异常。我们素描 两种实现。简单的一个, 源自语义学,和 基于相互递归的模块 这就允许单独汇编。

        5
  •  7
  •   Community CDub    7 年前

    首先,与查理的答案相反,这不是函数式编程的固有问题。OCAML的概念是 open unions or polymorphic variants 这基本上是你想要的。

    至于 为什么? 我相信这是哈斯克尔的选择,因为

    • 这使得类型可以被预测——对于每个类型,它们只是有限数量的构造函数。
    • 定义自己的类型很容易。
    • 许多Haskell函数是多态的,类允许您扩展自定义类型以适应函数参数(认为Java的接口)。

    所以如果你想 data Color r b g = Red r | Blue b | Green g 类型,它很容易生成,并且您可以很容易地使它像monad或functor,或者其他函数需要的那样。

        6
  •  6
  •   Luis Casillas    9 年前

    在这个(公认是老问题)问题上有一些很好的答案,但我觉得我必须投入我的几分钱。

    以后我无法在不修改此声明的情况下向此类型添加其他选项。那么这个系统有什么好处呢?看起来OO方式的可扩展性要大得多。

    我相信,答案是开放式和给你的那种可扩展性是 总是一个优点,相应地,事实是 军队 这对你来说是个弱点。

    封闭工会的优势在于 穷尽性 :如果您在编译时已经修复了所有的备选方案,那么您可以确定您的代码不会处理任何无法预料的情况。在许多问题域中,例如在语言的抽象语法树中,这是一个有价值的属性。如果您正在编写一个编译器,那么该语言的表达式就属于一个预定义的、封闭的子用例集合,您需要这样做。 希望人们能够在运行时添加编译器不理解的新子用例!

    事实上,编译器ASTS是访问者模式的四个激励示例的经典组合之一,它是封闭和和和穷尽模式匹配的OOP对应物。反思一下这样一个事实是很有启发性的:OO程序员最终发明了一种模式来恢复已关闭的金额。

    同样,程序和函数程序员也发明了模式来获得和的效果。最简单的是“函数记录”编码,它对应于OO接口。功能记录实际上是 调度表 . (注意,C程序员已经使用这种技术很久了!)诀窍是,给定类型的函数通常有大量可能的函数,通常无穷多。因此,如果您有一个字段是函数的记录类型,那么它可以很容易地支持一组天文数字般大或无限的替代项。而且,由于记录是在运行时创建的,并且可以根据运行时条件灵活地执行,所以替代方法是 晚绑定 .

    我最后的评论是,在我看来,OO让太多人相信可扩展性是 晚绑定 (例如,在运行时向类型添加新的子实例的能力),而这通常不是真的。晚绑定是 一种技术 为了扩展性。另一种方法是 作文 用积木的固定词汇和组装规则构建复杂对象。词汇表和规则在理想情况下是很小的,但是它们的设计使它们具有丰富的交互作用,允许您构建非常复杂的东西。

    函数式编程和ML/Haskell静态类型的风格,特别是长期以来强调组合而不是后期绑定。但在现实中,这两种技术都存在于两种范例中,并且应该在一个好的程序员的工具箱中。

    同样值得注意的是,编程语言本身从根本上就是组合的例子。编程语言有一个有限的、希望是简单的语法,允许您组合它的元素来编写任何可能的程序。(事实上,这可以追溯到上面的编译器/访问者模式示例,并激励它。)

        7
  •  2
  •   Christian Klauser    15 年前

    与面向对象的类相比,查看数据类型和类型类的另一种(或多或少)直观方法是:

    一个班 在OO语言中,表示两种具体类型 以及所有人的阶级 -类型:直接或间接派生自 .

    在OO语言中,您正好隐式地针对 -允许您“扩展”的类型 .

        8
  •  1
  •   Charlie Martin    15 年前

    好吧,这里的“open”是指“can be derived from”而不是ruby和smalltalk意义上的“open”,您可以在运行时用新方法扩展类,对吗?

    在任何情况下,注意两件事:第一,在大多数主要基于继承的OO语言中,有一种方法可以声明一个类来限制它被继承的能力。Java有“最后的”,在C++中有黑客。因此,它只是在其他OO语言中作为默认选项。

    其次,你 可以 仍然创建一个使用闭合ADT的新类型,并添加其他方法或不同的实现。所以你不是真的受到限制。同样,它们在形式上似乎具有相同的力量;你可以用一种语言表达的东西可以用另一种语言表达。

    实际上,函数式编程实际上是一种不同的范式(“模式”)。如果你期望它应该像一种OO语言,那么你会经常感到惊讶。