Java的接口和Haskell的types类:差异和相似之处?

当我正在学习Haskell的时候,我注意到了它的types类 ,它被认为是源自Haskell的一个伟大的发明。

但是,在维基百科页面上键入class :

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

这对我来说似乎与Java的界面相当接近(引用维基百科的界面(Java)页面 ):

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

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

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

编辑:我注意到, 即使haskell.org承认,他们是相似的 。 如果他们如此相似(或者他们?),那么为什么types会被这样的炒作对待呢?

更多编辑:哇,这么多伟大的答案! 我想我必须让社区决定哪一个是最好的。 但是,在阅读答案的同时,他们似乎只是说“类接口可以做很多事情,而接口不能或者不得不应对generics” 。 我不禁想知道,有什么接口可以做,而typeclasses不能? 另外,我注意到维基百科声称types类最初是在1989年发表的论文“如何使临时多态不那么临时”发明的,而Haskell仍然处于摇篮中,而Java项目于1991年开始,并于1995年首次发布那么也许不是typeclass与接口类似,反过来说,接口又受typestypes的影响? 有没有文件/文件支持或反驳呢? 感谢所有的答案,他们都非常有启发性!

感谢所有的投入!

我会说,一个接口有点像SomeInterface ttypes的类,其中所有的值的types都是t -> whatever (where不包含t )。 这是因为在Java和类似的语言中,这种types的inheritance关系依赖于被调用的对象的types,而不是其他types。

这意味着很难像add :: t -> t -> t使用一个接口,它在多于一个参数上是多态的,因为接口没有办法指定参数types和返回types该方法与被调用对象的types(即“self”types)是相同的types。 有了generics,有一些方法可以通过使用与对象本身相同types的generics参数来创build接口,比如Comparable<T>如何Foo implements Comparable<Foo> ,您希望在哪里使用Foo implements Comparable<Foo>所以compareTo(T otherobject)types的typest -> t -> Ordering 。 但是这仍然需要程序员遵循这个规则,当人们想要使用这个接口的函数时,也会引起头痛,他们必须有recursion的genericstypes参数。

而且,你不会有像empty :: t这样的东西,因为你不是在这里调用一个函数,所以它不是一个方法。

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

有了这个,这里有一些显着的差异:

  • 接口方法总是与一个对象实例相关联。 换句话说,总是有一个隐含的“this”参数,它是调用该方法的对象。 types类函数的所有input都是显式的。
  • 接口实现必须定义为实现接口的类的一部分。 相反,一个types的“实例”可以完全独立于其相关types定义,即使在另一个模块中也是如此。
  • types类允许您为任何已定义的操作定义“默认”实现。 接口只是严格的types规范,没有实现。

总的来说,我认为公平的说types类比接口更强大和更灵活。 你将如何定义一个接口将string转换为实现types的某个值或实例? 这当然不是不可能的,但结果不会是直观或优雅的。 你有没有希望有可能在一些编译库中实现一个types的接口? 这些都是很容易完成的types类。

types类被创build为结构化的方式来expression“ad-hoc多态性”,这基本上是重载函数的技术术语。 类的类定义如下所示:

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

这意味着,当你使用函数foo到属于类Foobar的types的某些参数时,它将查找特定于该types的foo的实现,并使用它。 这与C语言中运算符重载的情况非常相似,除了更灵活和通用。

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

另外值得注意的是types类具有更加灵活的调度方式 – 接口通常只应用于实现它的单个类,而types类是为types定义的, types可以出现在类的函数签名的任何地方。 OO接口中的等价物将允许接口定义将该类的对象传递给其他类的方法,定义静态方法和构造函数,根据调用上下文中需要的返回types来select实现,定义方法采用与实现接口的类相同types的参数,以及其他各种根本不能翻译的东西。

简而言之:它们的用途相似,但是它们的工作方式有所不同,而且typestypes更具performance力,在某些情况下,由于使用固定types而不是inheritance层次结构,因此使用起来更为简单。

观看Phillip Wadler的演讲信仰,演变和编程语言 。 Wadler在Haskell上工作,是Java Generics的主要贡献者。

阅读软件扩展和与types集成,其中给出了types类如何解决一些接口不能的问题的例子。

文中列举的例子有expression式问题,框架集成问题,独立可扩展性问题,主导分解,散乱和纠缠的暴政。

在编程大师们的脑海里 ,有一个关于Haskell的采访,Phil Wadler是types类的发明者,他解释了Java中的接口和Haskell中的types类之间的相似之处:

Java方法,如:

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

与Haskell方法非常相似:

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

所以,类的类与接口有关,但是实际的对应是一个静态的方法,用上面的types进行参数化。

我读过上面的答案。 我觉得我可以更清楚地回答:

一个Haskell“类”类和一个Java / C#“接口”或一个Scala“特质”基本上是类似的。 他们之间没有概念上的区别,但是存在执行上的差异:

  • Haskelltypes类是用与数据types定义分离的“实例”实现的。 在C#/ Java / Scala中,接口/特性必须在类定义中实现。
  • Haskelltypes类允许您返回一个这种types或自我types。 斯卡拉的特质也一样(this.type)。 请注意,Scala中的“自我types”是一个完全不相关的特性。 Java / C#需要一个凌乱的解决方法与generics近似这种行为。
  • Haskelltypes类让你定义没有input“this”types参数的函数(包括常量)。 Java / C#接口和Scala特性在所有函数上都需要“this”input参数。
  • Haskelltypes类让你定义函数的默认实现。 Scala特性和Java 8+接口也是如此。 C#可以用扩展方法来近似这样的东西。

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

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

这表明对于Eqtypes的实例,有两个操作,即等于(==)和不等于(/=) 。 但是不平等的操作是用等号来定义的(所以你只需要提供一个),反之亦然。

所以在可能不合法的Java中,这可能是这样的:

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

它的工作方式是只需要提供其中一种方法来实现接口。 所以我会说在界面层提供某种部分实现你想要的行为的能力是一个区别。

他们是相似的(阅读:有类似的用途),并可能类似实现:Haskell中的多态函数在引擎盖下列出与types类相关的函数。

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

但这是一个function表,而不是方法 。 方法绑定到一个对象,Haskelltypes类不是。

看到他们就像Java的generics。

正如丹尼尔所说,接口实现是从数据声明单独定义的。 正如其他人所指出的那样,有一种直接的方式来定义在多个地方使用相同的自由types的操作。 所以很容易将Num定义为一个types类。 因此,在Haskell中,我们得到了运算符重载的语法好处,而没有任何魔术重载运算符 – 只是标准的types类。

另一个区别是你可以使用基于types的方法,即使你还没有具体的值types悬而未决!

例如, read :: Read a => String -> a 。 所以如果你有足够的其他types的信息来说明你将如何使用“读”的结果,你可以让编译器找出你要使用哪个字典。

你也可以做一些事情,比如instance (Read a) => Read [a] where...可以让你为任何可读的事物列表定义一个读实例。 我不认为这在Java中是完全可能的。

而这一切都只是标准的单参数types类,没有欺骗性的继续。 一旦我们引入了多参数types类,那么就开辟了一个全新的可能性世界,对于函数依赖和types系列来说更是如此,这使得您可以在types系统中embedded更多的信息和计算。