为什么ADT好,继承不好?

one*_*one 21 java oop haskell programming-languages functional-programming

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

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

谢谢.

Tom*_*cek 28

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

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

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

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


Nor*_*sey 10

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

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

  • 使用代数数据类型,编译器可以(并且确实)告诉您是否忘记了案例或案例是多余的.当对事物进行更多操作而不是有各种各样事情时,这种能力特别有用.(例如,比代数数据类型更多的函数,或者比OO构造函数更多的方法.)在面向对象的语言中,如果将方法留在子类之外,编译器无法判断这是否是错误或是否有意继承超类方法不变.

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


PS你在读什么?我不知道有任何负责人说"ADT好,不好".

  • @Cheryl:我会支持你 - 我很高兴地说,从纯粹的OO观点来看,存在于许多语言中的继承几乎总是一个坏主意.泛型,接口,混合和对象组合的某种组合几乎总是更好的方法. (2认同)

Tom*_*ett 9

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

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

另外,请参阅对subtyping概念的这种反对意见.


Jon*_*rop 7

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

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

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

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

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

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

你真的想要ADT和接口继承.OCaml和F#等语言都提供.