Java的接口和Haskell的类型类:区别和相似之处?


112

当我学习Haskell时,我注意到它的type class,这应该是源自Haskell的伟大发明。

但是,在Wikipedia页面上,类型为class

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

这似乎与Java的界面非常接近对我来说,(引用Wikipedia的Interface(Java)页面):

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

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

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

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

更多编辑:哇,这么多好答案!我想我必须让社区决定哪一个是最好的。但是,在阅读答案时,所有的人似乎都只是说:“ typeclass可以做很多事情,而interface不能或不必应付泛型”。我不禁想知道,接口有什么可以做而类型类不能做吗?另外,我注意到Wikipedia声称typeclass最初是在1989年的论文中发明的*“如何使ad hoc多态性减少ad hoc”,而Haskell仍处于摇篮中,而Java项目始于1991年并于1995年首次发布因此,也许不是typetype与接口类似,而是接口受typeclass影响?是否有任何文件/文件支持或反对?感谢所有答案,它们都非常有启发性!

感谢您的所有投入!


3
不,没有什么接口可以做类型类不能做的事情,主要的警告是接口通常以具有Haskell所没有的内置功能的语言出现。如果将类型类添加到Java中,它们也将能够使用这些功能。
CA McCann

8
如果您有多个问题,则应提出多个问题,而不要将所有问题都塞入一个问题。无论如何,要回答您的最后一个问题:Java的主要影响是Objective-C(而不是经常被错误报告的C ++),而Java的主要影响又是Smalltalk和C。Java的接口是对Objective-C 协议的改编,而后者又是OO 中协议思想的形式化,而后者又基于网络中的协议思想,特别是ARPANet。这一切都发生在您引用的论文之前。...
约尔格W¯¯米塔格

1
... Haskell对Java的影响要晚得多,并且仅限于泛型,毕竟,泛型是由Haskell的一位设计师Phil Wadler共同设计的。
约尔格W¯¯米塔格

5
这是Java的原始设计师之一Patrick Naughton在Usenet上发表的一篇文章:Java受Objective-C而非C ++的强烈影响。不幸的是,由于年代久远,原始的帖子甚至都没有出现在Google的档案中。
约尔格W¯¯米塔格

8
有已关闭,因为这一个完全相同的副本另一个问题,但它有一个更深入的答案:stackoverflow.com/questions/8122109/...

Answers:


50

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

这意味着很难add :: t -> t -> t通过接口实现类似的功能,因为在一个以上的参数上它是多态的,因为接口无法指定该方法的参数类型和返回类型与的类型相同。被调用的对象(即“自我”类型)。使用Generics,有一种方法可以通过用通用参数(该参数应该与对象本身具有相同的类型)制作一个接口来伪造此伪装,例如Comparable<T>,它应如何使用,Foo implements Comparable<Foo>以便您compareTo(T otherobject)具有类型t -> t -> Ordering。但这仍然需要程序员遵循此规则,并且当人们想要创建使用此接口的函数时,还必须引起麻烦,因为他们必须具有递归的泛型类型参数。

同样,您不会有类似的事情,empty :: t因为您不在这里调用函数,因此它不是方法。


1
Scala特征(基本上是接口)允许使用this.type,因此您可以返回或接受“自我类型”的参数。Scala具有一个完全独立的功能,他们称之为“自我类型”,与此无关。这些都不是概念上的差异,只是实现上的差异。
粘土

45

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

顺便说一下,这里有一些明显的区别:

  • 接口方法始终与对象实例相关联。换句话说,总是存在一个隐含的“ this”参数,该参数是调用该方法的对象。类型类函数的所有输入都是显式的。
  • 接口实现必须定义为实现接口的类的一部分。相反,即使在另一个模块中,也可以将类型类“实例”与其关联的类型完全分开定义。

总的来说,我认为类型类比接口更强大和灵活。您将如何定义一个接口,用于将字符串转换为实现类型的某个值或实例?当然不是不可能,但是结果将不是直观的或优雅的。您是否曾经希望可以在某些已编译的库中为类型实现接口?这些都很容易用类型类完成。


1
您将如何扩展类型类?类型类可以像接口如何扩展接口一样扩展其他类型类吗?
CMCDragonkai 2014年

10
鉴于Java 8在接口中的默认实现,可能值得更新此答案。
恢复莫妮卡

1
@CMCDragonkai是的,例如,您可以说“ class(Foo a)=> Bar a where ...”来指定Bar类型类扩展了Foo类型类。像Java一样,Haskell在这里具有多重继承。
恢复莫妮卡

在这种情况下,类型类不是与Clojure中的协议相同但具有类型安全性吗?
nawfal

24

创建类型类是一种结构化的方式来表达“即席多态性”,这基本上是重载函数的技术术语。类型类定义看起来像这样:

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

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

接口在OO语言中具有相似的用途,但是基本概念有所不同。OO语言带有Haskell根本不具备的类型层次结构的内置概念,这在某种程度上使问题变得复杂,因为接口可能会通过子类型涉及两种重载(即,在适当的实例上调用方法,实现其超类型的接口的子类型)以及通过基于平面类型的调度(因为实现一个接口的两个类可能没有一个同时实现该接口的公共超类)。考虑到子类型带来的巨大额外复杂性,我建议将类型类视为非OO语言中重载函数的改进版本会更有帮助。

还要注意的是,类型类具有更灵活的分派方式-接口通常仅适用于实现它的单个类,而类型类是为类型定义的,它可以出现在类功能的签名中的任何位置。OO接口中与此等效的方法是,允许接口定义将该类的对象传递给其他类的方法,定义静态方法和构造函数,这些方法和构造函数将根据调用上下文中所需的返回类型来选择实现,并定义以下方法:接受与实现接口的类相同类型的参数,以及其他所有根本无法翻译的事物。

简而言之:它们具有相似的用途,但是它们的工作方式略有不同,并且由于使用固定类型而不是继承层次结构的各个部分,类型类都具有更强的表达能力,并且在某些情况下更易于使用。


我一直在努力理解Haskell中的类型层次结构,您如何看待Omega等类型系统的类型?他们可以模拟类型层次结构吗?
CMCDragonkai 2014年

@CMCDragonkai:我对Omega还不熟悉,真的不能说。
CA McCann 2014年

16

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

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

  • Haskell类型类是通过与数据类型定义分开的“实例”实现的。在C#/ Java / Scala中,必须在类定义中实现接口/特征。
  • Haskell类型类允许您返回此类型或自类型。Scala特征也是如此(this.type)。请注意,Scala中的“自我类型”是一个完全不相关的功能。Java / C#需要使用泛型的混乱解决方案来近似此行为。
  • Haskell类型类使您无需输入“ this”类型参数即可定义函数(包括常量)。Java / C#接口和Scala特性在所有函数上都需要一个“ this”输入参数。
  • Haskell类型类使您可以定义函数的默认实现。Scala特性和Java 8+接口也是如此。C#可以使用扩展方法来近似这样。

2
只是为了向这些回答要点添加一些文献,我今天阅读了(Haskell)类型类和(C#)接口,它们也与C#接口而不是Java进行了比较,但是应该在概念上更好地理解解剖概念。跨语言边界的界面。
daniel.kahlenberg

我认为对于默认实现之类的某些近似方法,在C#中使用抽象类可能会更有效地完成?
阿温

12

编程的大师头脑中,有一次关于Haskell的访谈,类型类的发明者Phil Wadler,他解释了Java接口与Haskell中的类型类之间的相似之处:

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

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


这应该是答案,因为根据Phil Wadler的主页,他是Haskell的首席设计师,与此同时,他还设计了Java的泛型扩展,该扩展后来包含在语言本身中。



7

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

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

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

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

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

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

并且它的工作方式是您只需提供这些方法之一即可实现该接口。因此,我想说,在接口级别提供所需行为的某种部分实现的能力是有区别的。


6

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

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

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

看到它们就像Java的泛型一样。


3

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

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

例如,read :: Read a => String -> a。因此,如果您还有足够的其他类型信息来使用,您将如何使用“读取”的结果,则可以让编译器找出要使用的词典。

您还可以执行类似的操作instance (Read a) => Read [a] where...,使您可以为任何可读内容列表定义读取实例。我认为这在Java中是不可能的。

所有这些只是标准的单参数类型类,没有任何麻烦。一旦我们引入了多参数类型类,就为可能性开辟了一个全新的世界,在功能依赖项和类型族方面,甚至更多,这使您可以在类型系统中嵌入更多信息和计算。


1
普通类型类适用于接口,而多参数类型类适用于OOP中的多调度。您会相应地增加编程语言的功能和程序员的头疼。
加利福尼亚州麦肯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.