代码之家  ›  专栏  ›  技术社区  ›  one-zero-zero-one

为什么ADT是好的继承是坏的?

  •  20
  • one-zero-zero-one  · 技术社区  · 14 年前

    我是一个很长时间的OO程序员和一个函数式编程新手。在我看来,代数数据类型只是继承的一个特殊情况,在我看来,您只有一个级别层次结构,而超级类不能扩展到模块之外。

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

    谢谢您。

    4 回复  |  直到 14 年前
        1
  •  26
  •   namin    14 年前

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

    • 自动测试系统 使添加用于处理现有类型的新功能变得容易
      • 您可以轻松地添加与ADT一起使用的新函数,ADT具有一组固定的事例。
      • 另一方面,添加新案例需要修改所有函数
    • 遗传 使您在具有固定功能时轻松添加新类型
      • 您可以轻松地创建继承的类并实现固定的虚拟函数集。
      • 另一方面,添加新的虚拟函数需要修改所有继承的类

    面向对象世界和功能世界都开发了允许其他类型可扩展性的方法。在haskell中,你可以使用typeclasses,在ml/ocaml中,人们可以使用函数字典或者(?)函数来获取 内在风格 可扩展性。另一方面,在OOP中,人们使用访问者模式,这本质上是一种获得类似ADT的方法。

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

        2
  •  9
  •   Norman Ramsey    14 年前

    托马斯佩特里切克的基本原理是完全正确的;你可能还想看看菲尔·韦德勒关于“表达问题”的文章。

    我们中的一些人喜欢代数数据类型而不喜欢继承还有两个原因:

    • 使用代数数据类型,编译器可以(并且确实)告诉您是否忘记了一个案例或者案例是否是多余的。当有更多的操作时,这个能力特别有用 东西 比那里有 种类 属于 事情 . (例如,比代数数据类型更多的函数,或者比OO构造函数更多的方法。)在面向对象的语言中,如果您将一个方法从子类中去掉,编译器就无法判断这是否是一个错误,或者您是否打算继承未更改的超类方法。

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


    另外,你在读什么?我不知道有哪个负责人说“好;坏”。

        3
  •  9
  •   Tom Crockett    14 年前

    在我的经验中,人们通常认为大多数OO语言实现的继承“不好”,不是继承本身的概念,而是继承 子类修改在超类中定义的方法的行为 (方法覆盖) 特别是在可变状态下 . 这真的是最后一部分,这才是关键。大多数OO语言将对象视为“封装状态”,这相当于允许对象内部状态的急剧变化。因此,当一个超类期望某个方法修改一个私有变量,而一个子类重写该方法来做完全不同的事情时,就会出现问题。这可能会引入一些微妙的错误,而编译器对此无能为力。

    注意,在Haskell的子类多态性实现中,不允许使用可变状态,因此您没有这样的问题。

    此外,参见 this objection 子类型的概念。

        4
  •  7
  •   J D    14 年前

    我是一个很长时间的OO程序员和一个函数式编程新手。在我看来,代数数据类型只是继承的一个特殊情况,在我看来,您只有一个级别层次结构,而超级类不能扩展到模块之外。

    您描述的是闭和类型,这是代数数据类型最常见的形式,如f和haskell所示。基本上,每个人都同意在类型系统中它们是一个有用的特性,主要是因为模式匹配使得通过形状和内容来分解它们变得容易,而且还因为它们允许用尽性和冗余性检查。

    然而,还有其他形式的代数数据类型。传统形式的一个重要限制是它们是封闭的,这意味着以前定义的闭和类型不能用新的类型构造函数扩展(更一般的问题称为“表达式问题”的一部分)。OCAML的多态变体允许开放和封闭的和类型,尤其是和类型的推理。相反,haskell和f_无法推断和类型。多态性变体解决了表达问题,它们非常有用。事实上,有些语言完全建立在可扩展代数数据类型上,而不是闭合和类型上。

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

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

    我相信您所指的是实现继承(即重写父类的功能),而不是接口继承(即实现一致的接口)。这是一个重要的区别。实现继承通常是不受欢迎的,而接口继承通常是受欢迎的(例如,在具有有限形式ADT的f中)。

    您真的需要ADT和接口继承。像ocaml和f这样的语言可以同时提供这两种语言。