为什么Haskell代数数据types“closures”?

纠正我,如果我错了,但它似乎像Haskell中的代数数据types在许多情况下,你会使用面向对象语言的类和inheritance有用。 但是有一个很大的区别:一旦声明了一个代数数据types,就不能在其他地方扩展。 这是“closures”。 在OO中,你可以扩展已经定义的类。 例如:

data Maybe a = Nothing | Just a 

没有办法,我可以以某种方式添加另一个选项,这种types稍后没有修改此声明。 那么这个系统有什么好处呢? 看来OO的方式会更具可扩展性。

ADT被closures的事实使得编写全部函数变得更容易。 这是总是产生一个结果的函数,为它的types的所有可能的值,例如。

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

如果Maybe是开放的,有人可以添加一个额外的构造函数,而maybeToList函数会突然中断。

在OO中,当你使用inheritance扩展一个types时,这不是一个问题,因为当你调用一个没有特定重载的函数时,它可以使用超类的实现。 也就是说,如果StudentPerson的子类,则可以使用Student对象调用printPerson(Person p)

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

 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 

现在, ==function是完全开放的,你可以添加自己的types,使其成为Eq类的一个实例。


请注意,有关可扩展数据types的想法已经有了,但是这绝对不是Haskell的一部分。

答案与代码容易扩展的方式有关,Phil Wadler称之为“expression式问题”的类与代数数据types之间的张力:

  • 用代数数据types,

    • 在事物上添加一个新的操作是非常便宜的 :你只需定义一个新的函数。 这些东西的所有旧function仍然保持不变。

    • 添加一种新的东西是非常昂贵 :你必须添加一个新的构造函数一个现有的数据types,你必须编辑和重新编译每个使用该types的函数

  • 通过课堂,

    • 添加一种新的东西非常便宜 :只需添加一个新的子类,并根据需要为所有现有的操作定义专门的方法。 超类和所有其他的子类继续工作。

    • 在事物上添加一个新的操作是非常昂贵的 :你必须向超类添加一个新的方法声明,并且可能为每个现存的子类添加一个方法定义 。 在实践中,负担因方法而异。

所以,代数数据types是封闭的,因为封闭types很好地支持某些types的程序演化。 例如,如果您的数据types定义了一种语言,那么很容易添加新的编译器通行证,而不会使旧通行证失效或更改数据。

有可能有“开放”的数据types,但除了在精心控制的情况下,types检查变得困难。 Todd Millstein在支持开放代数types和可扩展函数的语言devise上做了一些非常漂亮的工作 ,所有这些都使用模块化types检查器。 我发现他的论文很高兴阅读。

如果你写一个函数就好

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

那么你知道,它永远不会产生一个运行时错误,因为你已经涵盖了所有的情况。 只要Maybetypes是可扩展的,这就不再是真的了。 在那些需要可扩展types(而且比你想象的更稀有)的情况下,规范的Haskell解决scheme是使用types类。

选中“打开数据types并打开function” http://lambda-the-ultimate.org/node/1453

在面向对象的语言中,通过定义新的类很容易扩展数据,但添加新的函数却很困难。 在函数式语言中,情况是相反的:添加新函数不会造成任何问题,但是扩展数据(添加新的数据构造函数)需要修改现有的代码 。 支持可扩展性的两个方向的问题被称为expression问题 。 我们将开放数据types和打开函数作为Haskell语言中expression式问题的轻量级解决scheme。 这个想法是,开放数据types的构造函数和开放函数方程可以分散在整个程序中。 特别是,他们可能驻留在不同的模块中。 预期的语义如下:程序的行为就好像数据types和函数是closures的一样。 函数方程的阶数由最适合的模式匹配确定,其中特定模式优先于非特定模式。 我们显示我们的解决scheme适用于expression式问题,generics编程和exception。 我们草拟两个实现。 一个简单的,从语义派生的,一个基于相互recursion的模块,允许单独编译。

首先,与查理的回答相反,这不是函数式编程的本质。 OCaml拥有开放式联盟或多态变体的概念,它基本上是做你想做的。

至于为什么 ,我相信这个select是为了Haskell而做的

  • 这使得types是可以预测的 – 它们每个都只有有限的构造函数
  • 定义自己的types很容易。
  • 许多Haskell函数都是多态的,并且类允许扩展自定义types以适应函数参数(思考Java的接口)。

所以如果你宁愿有一个data Color rbg = Red r | Blue b | Green g data Color rbg = Red r | Blue b | Green g data Color rbg = Red r | Blue b | Green gtypes,很容易制作,你可以很容易地使它像一个monad或者一个functor或者其他function所需要的。

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

没有办法,我可以以某种方式添加另一个选项,这种types稍后没有修改此声明。 那么这个系统有什么好处呢? 看来OO的方式会更具可扩展性。

我相信这个答案是,开放款项给你的可扩展性并不总是一个加号,相应地,OO 强迫你这个事实是一个弱点。

封闭式工会的好处在于它们的详尽性 :如果在编译时已经确定了所有的替代scheme,那么您可以确定不会有无法预料的情况,即您的代码无法处理。 这是许多问题领域的宝贵财产,例如,语言的抽象语法树。 如果你正在编写一个编译器,那么这个语言的expression式就会落入一个预定义的,closures的子集中 – 你希望人们能够在运行时添加新的子类,这是你的编译器不理解的!

事实上,编译器AST是访问者模式的经典例子之一,这是与封闭和穷举模式匹配相对应的OOP。 OO程序员最终发明了一个恢复封闭模式的模式,这是有益的。

同样,程序和function程序员已经发明了模式来获得总和的效果。 最简单的是“functionlogging”编码,它对应于OO接口。 functionlogging实际上是一个调度表 。 (请注意,C程序员已经使用了这种技术很长时间了!)诀窍是,经常有大量的给定types的可能函数 – 通常是无限多的。 所以如果你有一个loggingtypes的字段是函数,那么它可以很容易地支持一个天文大或无限的替代scheme。 更重要的是,由于logging是在运行时创build的,并且可以基于运行时条件灵活地完成,所以备选scheme是延迟的

我所做的最后一个评论是,在我看来,OO让很多人认为可扩展性与后期绑定 (例如,在运行时向一个types添加新的子类的能力)是同义词,当这不是一般都是如此 后期绑定是可扩展性的一种技术 。 另一种技巧是用构build块的固定词汇表和复杂对象组合起来构build复杂对象。 词汇和规则是理想的小,但devise,使他们有丰富的互动,让你build立非常复杂的事情。

函数式编程 – 特别是ML / Haskell静态types化的风格 – 长期以来一直强调组合与后期绑定。 但实际上,这两种范式都存在这两种技术,应该是一个好程序员的工具箱。

另外值得注意的是,编程语言本身就是构成的基本例子。 一种编程语言有一个有限的,希望简单的语法,允许你组合它的元素来编写任何可能的程序。 (这实际上可以追溯到上面的编译器/访问者模式的例子,并激励它。)

另一种(或多或less)直观的查看数据types和types类与面向对象类的方法如下:

OO语言中的Foo类表示具体typesFoo以及所有Footypes的类别:直接或间接从Foo派生的类别。

在面向对象的语言中,你恰好隐式地对Footypes进行编程,这可以让你“扩展” Foo

好吧,在这里“打开”你的意思是“可以派生自”,而不是从Ruby和Smalltalk的意义上开放,你可以在运行时用新方法扩展一个类,对吧?

在任何情况下,请注意两点:首先,在大多数基于inheritance的OO语言中,有一种方法可以声明一个类来限制其inheritance的能力。 Java有“最终”,在C ++中有这样的黑客。 所以在这一点上,它只是作为其他面向对象语言的默认选项。

其次,您仍然可以创build一个使用closures的ADT的新types,并添加其他方法或不同的实现。 所以你不是那样受限制的 他们又似乎正式拥有同样的力量。 在一个可以expression的内容可以在另一个expression。

真正的事情是函数式编程确实是一个不同的范式(“模式”)。 如果你期望它应该像一个OO语言,你会定期感到惊讶。