oop - OOP 接口(interface)和 FP 类型类的区别

标签 oop haskell interface functional-programming typeclass

这个问题在这里已经有了答案:




9年前关闭。




Possible Duplicate:
Java's Interface and Haskell's type class: differences and similarities?



当我开始学习 Haskell 时,有人告诉我类型类与接口(interface)不同,而且功能更强大。

一年后,我广泛使用了接口(interface)和类型类,但我还没有看到有关它们有何不同的示例或解释。这要么不是自然而然的启示,要么我错过了一些明显的东西,或者实际上没有真正的区别。

在互联网上搜索并没有发现任何实质性的东西。所以,你有答案吗?

最佳答案

你可以从多个角度来看这一点。其他人会不同意,但我认为 OOP 接口(interface)是了解类型类的好起点(当然与从零开始相比)。

人们喜欢指出,从概念上讲,类型类对类型进行分类,很像集合——“支持这些操作的类型集,以及无法在语言本身中编码的其他期望”。这是有道理的,偶尔会声明一个没有方法的类型类,说“只有在满足某些要求时才使您的类型成为此类的实例”。 OOP 接口(interface)很少发生这种情况1。

就具体差异而言,类型类比 OOP 接口(interface)更强大的方式有多种:

  • 最大的一个是类型类将类型实现接口(interface)的声明与类型本身的声明解耦。使用 OOP 接口(interface),您在定义类型时会列出类型实现的接口(interface),并且以后无法添加更多。使用类型类,如果你创建一个新的类型类,给定类型“在模块层次结构中”可以实现但不知道,你可以编写一个实例声明。如果您有来自不同第三方的类型和类型类,它们彼此不了解,您可以为它们编写实例声明。在 OOP 接口(interface)的类似情况下,您大多只是被卡住了,尽管 OOP 语言已经进化出“设计模式”(适配器)来解决这个限制。
  • 下一个最大的问题(这当然是主观的)是,虽然从概念上讲,OOP 接口(interface)是一组可以在实现该接口(interface)的对象上调用的方法,类型类是一组可以与以下类型一起使用的方法类(class)成员。区别很重要。因为类型类方法是根据类型而不是对象定义的,所以使用该类型的多个对象作为参数(相等和比较运算符)或返回该类型的对象作为结果的方法没有任何障碍(各种算术运算),甚至类型的常量(最小和最大界限)。 OOP 接口(interface)无法做到这一点,OOP 语言已经进化出设计模式(例如虚拟克隆方法)来解决这个限制。
  • OOP接口(interface)只能为类型定义;还可以为所谓的“类型构造函数”定义类型类。在各种 C 派生的 OOP 语言中使用模板和泛型定义的各种集合类型是类型构造函数:List 将类型 T 作为参数并构造类型 List<T>。类型类让您可以为类型构造函数声明接口(interface):比如说,集合类型的映射操作,它在集合的每个元素上调用提供的函数,并将结果收集到集合的新副本中 - 可能具有不同的元素类型!同样,您不能使用 OOP 接口(interface)执行此操作。
  • 如果给定的参数需要实现多个接口(interface),使用类型类很容易列出它应该属于哪些接口(interface);对于 OOP 接口(interface),您只能指定一个接口(interface)作为给定指针或引用的类型。如果你需要它来实现更多,你唯一的选择就是不吸引人的选择,比如在签名中编写一个接口(interface)并转换为其他接口(interface),或者为每个接口(interface)添加单独的参数并要求它们指向同一个对象。你甚至不能通过声明一个新的、从你需要的接口(interface)继承的空接口(interface)来解决它,因为一个类型不会因为它实现了它的祖先而自动被视为实现了你的新接口(interface)。 (如果你可以事后声明实现,这不会是一个问题,但是是的,你也不能这样做。)
  • 与上述情况相反,您可以要求两个参数具有实现特定接口(interface)的类型,并且它们是相同的类型。对于 OOP 接口(interface),您只能指定第一部分。
  • 类型类的实例声明更加灵活。对于 OOP 接口(interface),您只能说“我声明了一个类型 X,它实现了接口(interface) Y”,其中 X 和 Y 是特定的。对于类型类,您可以说“元素类型满足这些条件的所有 List 类型都是 Y 的成员”。 (你也可以说“所有属于 X​​ 和 Y 的类型也是 Z 的成员”,尽管在 Haskell 中这是有问题的,原因有很多。)
  • 所谓的“父类(super class)约束”比单纯的接口(interface)继承更灵活。对于 OOP 接口(interface),你只能说“一个类型要实现这个接口(interface),它也必须实现这些其他接口(interface)”。这也是类型类最常见的情况,但是父类(super class)约束也让你说“SomeTypeConstructor 必须实现某某接口(interface)”或“应用于该类型的该类型函数的结果必须满足某某接口(interface)”约束”等。
  • 目前这是 Haskell 中的语言扩展(与类型函数一样),但您可以声明涉及多种类型的类型类。例如,同构类:类型对的类,您可以在其中相互转换并返回而不会丢失信息。同样,使用 OOP 接口(interface)是不可能的。
  • 我确定还有更多。

  • 值得注意的是,在添加泛型的 OOP 语言中,可以消除其中一些限制(第四、第五、可能是第二点)。

    另一方面,OOP 接口(interface)可以做而类型类本身不能做的有两件重要的事情:
  • 运行时动态调度。在 OOP 语言中,传递和存储指向实现接口(interface)的对象的指针并在运行时调用它的方法是微不足道的,这些方法将根据对象的动态运行时类型进行解析。相比之下,默认情况下,类型类约束都是在编译时确定的——也许令人惊讶的是,在绝大多数情况下,这就是您所需要的。如果您确实需要动态调度,则可以使用所谓的存在类型(目前是 Haskell 中的语言扩展):一种“忘记”对象类型的构造,并且只记住(由您选择)它遵守某些类型类约束。从这一点来看,它的行为方式与指向在 OOP 语言中实现接口(interface)的对象的指针或引用完全相同,并且类型类在这方面没有任何缺陷。 (需要指出的是,如果你有两个existentials实现了同一个类型的类,而一个type类方法需要它的类型的两个参数,你就不能使用existentials作为参数,因为你不知道存在性具有相同的类型。但与 OOP 语言相比,首先不能有这样的方法,这没有损失。)
  • 对象到接口(interface)的运行时转换。在 OOP 语言中,您可以在运行时获取一个指针或引用并测试它是否实现了一个接口(interface),如果实现了,则将其“强制转换”到该接口(interface)。类型类本身没有任何等效的东西(这在某些方面是一个优势,因为它保留了一个名为 parametricity 的属性,但我不会在这里讨论)。当然,没有什么能阻止您添加一个新的类型类(或扩充一个现有的类),并使用方法将类型的对象转换为您想要的任何类型类的存在项。 (你也可以更通用地将这样的功能实现为一个库,但它涉及的内容要多得多。我计划有一天完成它并将其上传到 Hackage,我保证!)

  • 我应该指出,虽然您可以做这些事情,但许多人认为以这种方式模拟 OOP 是一种糟糕的风格,并建​​议您使用更直接的解决方案,例如函数的显式记录而不是类型类。凭借完整的一流功能,该选项同样强大。

    在操作上,OOP 接口(interface)通常通过在对象本身中存储一个或多个指针来实现,这些指针指向对象实现的接口(interface)的函数指针表。类型类通常是通过“字典传递”实现的(对于通过装箱进行多态的语言,如 Haskell,而不是通过多实例化的多态,如 C++):编译器隐式地将指针传递给函数表(和常量) ) 作为每个使用类型类的函数的隐藏参数,无论涉及多少对象,该函数都会获得一个副本(这就是为什么您可以执行上面第二点中提到的事情)。存在类型的实现看起来很像 OOP 语言所做的:指向类型类字典的指针与对象一起存储,作为“被遗忘的”类型是它的成员的“证据”。

    如果你曾经读过 C++ 的“概念”提案(因为它最初是为 C++11 提出的),它基本上是为 C++ 的模板重新构想的 Haskell 类型类。有时我认为拥有一种简单地采用带有 C++ 概念的语言,将面向对象和虚函数的一半撕掉,清理语法和其他缺陷,并在需要运行时添加存在类型的语言会很好基于类型的动态调度。 (更新:Rust 基本上就是这个,还有很多其他的好东西。)

    1 Serializable 在 Java 中是一个没有方法或字段的接口(interface),因此是罕见的事件之一。

    关于oop - OOP 接口(interface)和 FP 类型类的区别,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8122109/

    相关文章:

    java - 从父类类型方法返回子对象

    java - 静态方法与否?全局变量?

    haskell - 当你依赖一个 cabal 包时,它似乎会在所有 cabal 节中引入依赖关系

    haskell - 我应该使用镜头中的什么来按索引构建只读 setter/getter ?

    Typescript 类实现接口(interface)不尊重 readonly 修饰符

    没有对象的 MATLAB 构造函数/接口(interface)类

    java - 在 Java 中将值附加到列表的元素

    haskell - Websockets + 仆人服务器

    .net - 通用约束是否应该优先于使用接口(interface)作为参数类型?

    C# 公共(public)接口(interface)与接口(interface)