Java 的接口和 Haskell 的类型类:异同?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6948166/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 17:59:45  来源:igfitidea点击:

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

javahaskellinterfacetypeclass

提问by zw324

While I am learning Haskell, I noticed its type class, which is supposed to be a great invention that originated from Haskell.

在学习 Haskell 时,我注意到它的类型 class,这应该是源自 Haskell 的一项伟大发明。

However, in the Wikipedia page on type class:

但是,在类型类的维基百科页面中

The programmer defines a type class by specifying a set of function or constant names, together with their respective types, that must exist for every type that belongs to the class.

程序员通过指定一组函数或常量名称以及它们各自的类型来定义类型类,这些名称对于属于该类的每个类型都必须存在。

Which seems rather close to Java's Interfaceto me (quoting Wikipedia's Interface(Java) page):

这对我来说似乎很接近Java 的接口(引用Wikipedia 的 Interface(Java) 页面):

An interface in the Java programming language is an abstract type that is used to specify an interface (in the generic sense of the term) that classes must implement.

Java 编程语言中的接口是一种抽象类型,用于指定类必须实现的接口(在术语的一般意义上)。

These two looks rather similar: type class limit a type's behavior, while interface limit a class' behavior.

这两个看起来很相似:类型类限制了类型的行为,而接口限制了类的行为。

I wonder what are the differences and similarities between type class in Haskell and interface in Java, or maybe they are fundamentally different?

我想知道 Haskell 中的类型类和 Java 中的接口之间有什么区别和相似之处,或者它们根本不同?

EDIT:I noticed even haskell.org admits that they are similar. If they are so similar (or are they?), then why type class is treated with such hype?

编辑:我注意到甚至 haskell.org 也承认它们是相似的。如果它们如此相似(或者它们是?),那么为什么类型类会被如此炒作?

MORE EDIT:Wow, so many great answers! I guess I'll have to let the community decide which is the best one. However, while reading the answers, all of them seem to just say that "there are many things typeclass can do while interface cannot or have to cope with generics". I cannot help but wondering, are there anything interfaces can do while typeclasses cannot?Also, I noticed that Wikipedia claims that typeclass was originally invented in the 1989 paper *"How to make ad-hoc polymorphism less ad hoc", while Haskell is still in its cradle, while Java project was started in 1991 and first released in 1995. So maybe instead of typeclass being similar to interfaces, its the other way around, that interfaces were influenced by typeclass?Are there any documents/papers support or disprove this? Thanks for all the answers, they are all very enlightening!

更多编辑:哇,这么多很棒的答案!我想我必须让社区来决定哪个是最好的。然而,在阅读答案时,他们似乎都只是说“类型类可以做很多事情,而接口不能或必须处理泛型”。我不禁想知道,有什么接口可以做而类型类不能做的吗?另外,我注意到维基百科声称 typeclass 最初是在 1989 年的论文 *"How to make ad-hoc polymorphism less ad hoc" 中发明的,而 Haskell 仍处于摇篮中,而 Java 项目始于 1991 年,并于 1995 年首次发布. 所以也许不是 typeclass 类似于接口,相反,接口受 typeclass 的影响?是否有任何文件/论文支持或反驳这一点?感谢所有的答案,他们都非常有启发性!

Thanks for all the inputs!

感谢所有的投入!

采纳答案by newacct

I would say that an interface is kind of like a type class SomeInterface twhere all of the values have the type t -> whatever(where whateverdoes not contain t). This is because with the kind of inheritance relationship in Java and similar languages, the method called depends on the type of object they are called on, and nothing else.

我会说接口有点像类型类SomeInterface t,其中所有值都具有类型t -> whatever(其中whatever不包含t)。这是因为在 Java 和类似语言中的那种继承关系,调用的方法取决于它们被调用的对象的类型,而不是别的。

That means it's really hard to make things like add :: t -> t -> twith an interface, where it is polymorphic on more than one parameter, because there's no way for the interface to specify that the argument type and return type of the method is the same type as the type of the object it is called on (i.e. the "self" type). With Generics, there are kinda ways to fake this by making an interface with generic parameter that is expected to be the same type as the object itself, like how Comparable<T>does it, where you are expected to use Foo implements Comparable<Foo>so that the compareTo(T otherobject)kind of has type t -> t -> Ordering. But that still requires the programmer to follow this rule, and also causes headaches when people want to make a function that uses this interface, they have to have recursive generic type parameters.

这意味着很难add :: t -> t -> t用一个接口来做这样的事情,它在多个参数上是多态的,因为接口无法指定方法的参数类型和返回类型与类型相同它被调用的对象(即“self”类型)。使用泛型,有一些方法可以通过创建一个带有泛型参数的接口来伪造这一点,该接口预计与对象本身的类型相同,比如Comparable<T>它是怎么做的,你应该在哪里使用,Foo implements Comparable<Foo>以便compareTo(T otherobject)类型具有 type t -> t -> Ordering。但这仍然需要程序员遵循这个规则,而且当人们想要制作一个使用这个接口的函数时,他们必须具有递归的泛型类型参数,这也令人头疼。

Also, you won't have things like empty :: tbecause you're not calling a function here, so it isn't a method.

此外,你不会有这样的事情,empty :: t因为你没有在这里调用一个函数,所以它不是一个方法。

回答by Daniel Pratt

What is similar between interfaces and type classes is that they name and describe a set of related operations. The operations themselves are described via their names, inputs, and outputs. Likewise there may be many implementations of these operations that will likely differ in their implementation.

接口和类型类之间的相似之处在于它们命名和描述了一组相关的操作。操作本身通过它们的名称、输入和输出来描述。同样,这些操作可能有许多实现方式,它们的实现方式可能会有所不同。

With that out of the way, here are some notable differences:

顺便说一下,这里有一些显着的差异:

  • Interfaces methods are always associated with an object instance. In other words, there is always an implied 'this' parameter that is the object on which the method is called. All inputs to a type class function are explicit.
  • An interface implementation must be defined as part of the class that implements the interface. Conversely, a type class 'instance' can be defined completely seperate from its associated type...even in another module.
  • 接口方法总是与对象实例相关联。换句话说,总是有一个隐含的“this”参数,它是调用方法的对象。类型类函数的所有输入都是显式的。
  • 接口实现必须定义为实现接口的类的一部分。相反,类型类“实例”可以与其关联类型完全分开定义……甚至在另一个模块中。

In general, I think its fair to say that type classes are more powerful and flexible than interfaces. How would you define an interface for converting a string to some value or instance of the implementing type? It's certainly not impossible, but the result would not be intuitive or elegant. Have you ever wished it was possible to implement an interface for a type in some compiled library? These are both easy to accomplish with type classes.

总的来说,我认为可以公平地说类型类比接口更强大和灵活。您将如何定义将字符串转换为实现类型的某个值或实例的接口?这当然不是不可能,但结果不会是直观或优雅的。您是否曾经希望可以为某个编译库中的类型实现接口?这些都可以通过类型类轻松实现。

回答by C. A. McCann

Type classes were created as a structured way to express "ad-hoc polymorphism", which is basically the technical term for overloaded functions. A type class definition looks something like this:

类型类是作为一种结构化方式来表达“即席多态性”而创建的,这基本上是重载函数的技术术语。类型类定义如下所示:

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

What this means is that, when you use apply the function footo some arguments of a type that belong to the class Foobar, it looks up an implementation of foospecific to that type, and uses that. This is very similar to the situation with operator overloading in languages like C++/C#, except more flexible and generalized.

这意味着,当您将函数foo应用于属于 class 的类型的某些参数时Foobar,它会查找foo特定于该类型的实现,并使用它。这与 C++/C# 等语言中运算符重载的情况非常相似,只是更加灵活和通用。

Interfaces serve a similar purpose in OO languages, but the underlying concept is somewhat different; OO languages come with a built-in notion of type hierarchies that Haskell simply doesn't have, which complicates matters in some ways because interfaces can involve both overloading by subtyping (i.e., calling methods on appropriate instances, subtypes implementing interfaces their supertypes do) and by flat type-based dispatch (since two classes implementing an interface may not have a common superclass that also implements it). Given the huge additional complexity introduced by subtyping, I suggest it's more helpful to think of type classes as an improved version of overloaded functions in a non-OO language.

接口在面向对象语言中的用途类似,但底层概念有些不同;OO 语言带有 Haskell 根本没有的内置类型层次结构概念,这在某些方面使问题复杂化,因为接口可能涉及通过子类型进行重载(即,在适当的实例上调用方法,子类型实现其超类型所做的接口)并通过基于平面类型的调度(因为实现一个接口的两个类可能没有一个也实现它的公共超类)。鉴于子类型引入的巨大额外复杂性,我建议将类型类视为非 OO 语言中重载函数的改进版本更有帮助。

Also worth noting is that type classes have vastly more flexible means of dispatch--interfaces generally apply only to the single class implementing it, whereas type classes are defined for a type, which can appear anywhere in the signature of the class's functions. The equivalent of this in OO interfaces would be allowing the interface to define ways to pass an object of that class to other classes, define static methods and constructors that would select an implementation based on what return typeis required in calling context, define methods that take arguments of the same type as the class implementing the interface, and various other things that don't really translate at all.

还值得注意的是,类型类具有更加灵活的调度方式——接口通常仅适用于实现它的单个类,而类型类是为类型定义的,它可以出现在类函数的签名中的任何位置。在 OO 接口中的等价物是允许接口定义将那个类的对象传递给其他类的方法,定义静态方法和构造函数,它们将根据调用上下文中所需的返回类型选择实现,定义方法使用与实现接口的类相同类型的参数,以及根本没有真正转换的各种其他东西。

In short: They serve similar purposes, but the way they work is somewhat different, and type classes are both significantly more expressive and, in some cases, simpler to use because of working on fixed types rather that pieces of an inheritance hierarchy.

简而言之:它们服务于相似的目的,但它们的工作方式有些不同,并且类型类都明显更具表现力,并且在某些情况下,由于处理固定类型而不是继承层次结构的部分,因此使用起来更简单。

回答by clay

I've read the above answers. I feel I can answer slightly more clearly:

我已阅读以上答案。我觉得我可以回答得更清楚一点:

A Haskell "type class" and a Java/C# "interface" or a Scala "trait" are basically analogous. There is no conceptual distinction between them but there are implementation differences:

Haskell“类型类”和Java/C#“接口”或Scala“特性”基本上是类似的。它们之间没有概念上的区别,但存在实现差异:

  • Haskell type classes are implemented with "instances" that are separate from the data type definition. In C#/Java/Scala, the interfaces/traits must be implemented in the class definition.
  • Haskell type classes allow you to return a this type or self type. Scala traits do as well (this.type). Note that "self types" in Scala are a completely unrelated feature. Java/C# require a messy workaround with generics to approximate this behavior.
  • Haskell type classes let you define functions (including constants) without an input "this" type parameter. Java/C# interfaces and Scala traits require a "this" input parameter on all functions.
  • Haskell type classes let you define default implementations for functions. So do Scala traits and Java 8+ interfaces. C# can approximate something like this with extensions methods.
  • Haskell 类型类是用与数据类型定义分开的“实例”实现的。在 C#/Java/Scala 中,接口/特征必须在类定义中实现。
  • Haskell 类型类允许您返回 this 类型或 self 类型。Scala trait 也是如此(this.type)。请注意,Scala 中的“自类型”是一个完全不相关的特性。Java/C# 需要使用泛型的混乱解决方法来近似此行为。
  • Haskell 类型类允许您在没有输入“this”类型参数的情况下定义函数(包括常量)。Java/C# 接口和 Scala 特征要求所有函数都有一个“this”输入参数。
  • Haskell 类型类允许您定义函数的默认实现。Scala 特征和 Java 8+ 接口也是如此。C# 可以使用扩展方法来近似这样的东西。

回答by ewernli

In Master minds of Programming, there's an interview about Haskell with Phil Wadler, the inventor of type classes, who explain the similarities between interfaces in Java and type classes in Haskell:

Master minds of Programming 中,有一个关于 Haskell 的采访,Phil Wadler,类型类的发明者,他解释了 Java 中的接口和 Haskell 中类型类之间的相似之处:

A Java method like:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

is very similar to the Haskell method:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

一个 Java 方法,如:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

与 Haskell 方法非常相似:

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

So, type classes are related to interfaces, but the real correspondance would be a static method parametrized with a type as above.

因此,类型类与接口相关,但真正的对应关系是使用上述类型参数化的静态方法。

回答by mcandre

Watch Phillip Wadler's talk Faith, Evolution, and Programming Languages. Wadler worked on Haskell and was a major contributor to Java Generics.

观看 Phillip Wadler 的演讲Faith, Evolution, and Programming Languages。Wadler 曾在 Haskell 上工作,并且是 Java 泛型的主要贡献者。

回答by Channing Walton

Read Software Extension and Integration with Type Classeswhere examples are given of how type classes can solve a number of problems that interfaces cannot.

阅读软件扩展和与类型类的集成,其中给出了类型类如何解决接口无法解决的许多问题的示例。

Examples listed in the paper are:

论文中列出的例子有:

  • the expression problem,
  • the framework integration problem,
  • the problem of independent extensibility,
  • the tyranny of the dominant decomposition, scattering and tangling.
  • 表达问题,
  • 框架集成问题,
  • 独立可扩展性问题,
  • 占主导地位的分解、分散和缠结的暴政。

回答by Chris

I can't speak to the "hype"-level, if it seems that way fine. But yes type classes are similar in lots of ways. One difference that I can think of is that it Haskell you can provide behavior for some of the type class's operations:

我不能说“炒作”级别,如果它看起来不错的话。但是是的类型类在很多方面都很相似。我能想到的一个区别是 Haskell 可以为某些类型类的操作提供行为:

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

which shows that there are two operations, equal (==), and not-equal (/=), for things that are instances of the Eqtype class. But the not-equal operation is defined in terms of equals (so that you'd only have to provide one), and vice versa.

这表明对于属于类型类的实例的事物有两个操作,equal(==)和 not-equal 。但是不等于操作是根据等于定义的(因此您只需要提供一个),反之亦然。(/=)Eq

So in probably-not-legal-Java that would be something like:

因此,在可能不合法的 Java 中,这将类似于:

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

and the way that it would work is that you'd only need to provide one of those methods to implement the interface. So I'd say that the ability to provide a sort of partial implemention of the behavior you want at the interfacelevel is a difference.

它的工作方式是您只需要提供其中一种方法来实现接口。所以我想说,在接口级别提供一种你想要的行为的部分实现的能力是不同的。

回答by Alexandre C.

They are similar (read: have similar use), and probably implemented similarly: polymorphic functions in Haskell take under the hood a 'vtable' listing the functions associated with the typeclass.

它们是相似的(阅读:具有相似的用途),并且可能以相似的方式实现:Haskell 中的多态函数在幕后采用“vtable”列出与类型类关联的函数。

This table can often be deduced at compile time. This is probably less true in Java.

这个表通常可以在编译时推导出来。这在 Java 中可能不太正确。

But this is a table of functions, not methods. Methods are bound to an object, Haskell typeclasses are not.

但这是一个函数表,而不是方法表。方法绑定到一个对象,而 Haskell 类型类则没有。

See them rather like Java's generics.

把它们看成 Java 的泛型。

回答by sclv

As Daniel says, interface implementations are defined seperatelyfrom data declarations. And as others have pointed out, there's a straightforward way to define operations that use the same free type in more than one place. So its easy to define Numas a typeclass. Thus in Haskell we get the syntactic benefits of operator overloading without actually having any magic overloaded operators -- just standard typeclasses.

正如 Daniel 所说,接口实现与数据声明是分开定义的。正如其他人指出的那样,有一种直接的方法可以定义在多个地方使用相同自由类型的操作。所以很容易定义Num为类型类。因此,在 Haskell 中,我们获得了运算符重载的语法优势,而实际上没有任何神奇的重载运算符——只是标准类型类。

Another difference is that you can use methods based on a type, even when you don't have a concrete value of that type hanging around yet!

另一个区别是您可以使用基于类型的方法,即使您还没有该类型的具体值!

For example, read :: Read a => String -> a. So if you have enough other type information hanging around about how you'll use the result of a "read", you can let the compiler figure out which dictionary to use for you.

例如,read :: Read a => String -> a。因此,如果您有足够多的关于如何使用“读取”结果的其他类型信息,您可以让编译器确定要为您使用的字典。

You can also do things like instance (Read a) => Read [a] where...which lets you define a read instance for anylist of readable things. I don't think that's quite possible in Java.

您还可以执行诸如instance (Read a) => Read [a] where...让您为任何可读事物列表定义读取实例之类的操作。我认为这在 Java 中不太可能。

And all this is just standard single-parameter typeclasses with no trickery going on. Once we introduce multi-parameter typeclasses, then a whole new world of possibilities opens up, and even more so with functional dependencies and type families, which let you embed much more information and computation in the type system.

所有这些都只是标准的单参数类型类,没有任何诡计。一旦我们引入了多参数类型类,就会打开一个全新的可能性世界,函数依赖和类型家族更是如此,它们可以让您在类型系统中嵌入更多信息和计算。