为什么ADT好,继承不好?

时间:2010-07-17 14:53:29

标签: java oop programming-languages haskell functional-programming

我是OO程序员和功能编程新手。从我的小曝光代数数据类型看起来只是一个特殊的继承对我来说,你只有一个级别的层次结构,超级类不能扩展到模块之外。

所以我的(可能是愚蠢的)问题是:如果ADT只是那个,一个特殊的继承案例(再次这个假设可能是错误的;请在那种情况下纠正我),那么为什么继承得到所有的批评和ADT得到所有的赞美?

谢谢。

4 个答案:

答案 0 :(得分:27)

我认为ADT是对继承的补充。它们都允许您创建可扩展代码,但可扩展性的工作方式不同:

  • ADT 可让您轻松添加新功能以使用现有类型
    • 您可以轻松添加适用于ADT的新功能,该功能具有固定的一组案例
    • 另一方面,添加新案例需要修改所有功能
  • 继承可让您在拥有固定功能时轻松添加新类型
    • 您可以轻松创建继承的类并实现固定的虚拟函数集
    • 另一方面,添加新的虚函数需要修改所有继承的类

面向对象的世界和功能世界都开发了允许其他类型可扩展性的方法。在Haskell中,你可以使用类型类,在ML / OCaml中,人们会使用函数字典或者(?)仿函数来获得 inhertiance-style 的可扩展性。另一方面,在OOP中,人们使用访问者模式,这实际上是一种获取类似ADT的方式。

通常的编程模式在OOP和FP中是不同的,因此当您使用函数式语言进行编程时,您将以更需要功能样式可扩展性的方式编写代码(同样在OOP中)。在实践中,我认为有一种语言可以让你根据你想要解决的问题使用这两种风格,这很棒。

答案 1 :(得分:9)

Tomas Petricek完全正确地掌握了基本面;你可能也想看看Phil Wadler关于“表达问题”的写作。

我们有些人更喜欢代数数据类型而不是继承,还有另外两个原因:

  • 使用代数数据类型,编译器可以(并且确实)告诉您是否忘记了案例或案例是多余的。当 thing 上的操作多于 thing 时,此功能特别有用。 (例如,比代数数据类型更多的函数,或者比OO构造函数更多的方法。)在面向对象的语言中,如果将方法保留在子类之外,编译器无法判断这是错误还是您打算继承超类方法不变。

  • 这个更主观:很多人都注意到如果正确使用继承,那么算法的实现很容易被涂抹在六个以上的类中,甚至还有一个很好的类浏览器可能很难遵循程序的逻辑(数据流和控制流)。如果没有一个好的类浏览器,你就没有机会了。如果你想看到一个很好的例子,尝试在Smalltalk中实现bignums,并在溢出时自动故障转移到bignums。这是一个很好的抽象,但语言使得实现难以遵循。使用代数数据类型的函数,算法的逻辑通常都在一个地方,或者如果它被拆分,它就会分成具有易于理解的契约的函数。


P.S。你在读什么?我不知道有任何负责人说“ADT好,不好。”

答案 2 :(得分:9)

根据我的经验,人们通常认为大多数面向对象语言实现的继承“坏”并不是继承本身的想法,而是子类修改超类中定义的方法行为的想法 (方法覆盖),特别是在存在可变状态的情况下。这真是最后一部分是踢球者。大多数OO语言将对象视为“封装状态”,这相当于允许对象内部状态的猖獗变异。因此,例如,当超类期望某个方法修改私有变量时会出现问题,但是子类会覆盖该方法以执行完全不同的操作。这可能会引入编译器无法阻止的细微错误。

请注意,在Haskell的子类多态的实现中,不允许使用可变状态,因此您不会遇到此类问题。

另外,请参阅this objection以了解子类型的概念。

答案 3 :(得分:7)

  

我是OO程序员和功能编程新手。从我的小曝光代数数据类型看起来只是一个特殊的继承对我来说,你只有一个级别的层次结构,超级类不能扩展到模块之外。

您正在描述闭合和类型,这是代数数据类型的最常见形式,如F#和Haskell中所示。基本上,每个人都认为它们在类型系统中是一个有用的功能,主要是因为模式匹配使得通过形状和内容来分析它们变得容易,并且因为它们允许详尽和冗余检查。

但是,还有其他形式的代数数据类型。传统形式的一个重要限制是它们是封闭的,这意味着先前定义的闭合和类型不能用新类型构造函数扩展(更普遍的问题的一部分称为“表达式问题”)。 OCaml的多态变体允许开放和闭合和类型,特别是和类型的推断。相反,Haskell和F#不能推断和类型。多态变体解决了表达问题,它们非常有用。事实上,有些语言完全是基于可扩展的代数数据类型而不是封闭的和类型。

在极端情况下,你也有像Mathematica这样的语言,其中“一切都是表达”。因此,类型系统中唯一的类型形成了一个普通的“单例”代数。这是“可扩展的”,因为它是无限的,并且,它最终以完全不同的编程风格达到顶峰。

  

所以我的(可能是愚蠢的)问题是:如果ADT只是那个,一个特殊的继承案例(再次这个假设可能是错误的;请在那种情况下纠正我),那么为什么继承得到所有的批评和ADT得到所有的赞美?

我相信你特指的是实现继承(即来自父类的覆盖功能),而不是接口继承(即实现一致的接口)。这是一个重要的区别。实现继承经常被讨厌,而界面继承经常被喜欢(例如在F#中,它具有有限形式的ADT)。

你真的想要ADT和接口继承。 OCaml和F#等语言都提供了这两种语言。