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

时间:2011-08-04 20:32:45

标签: java haskell interface typeclass

在我学习Haskell的过程中,我注意到它的类型类,这应该是一个源自Haskell的伟大发明。

然而,在the Wikipedia page on type class

  

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

这似乎与 Java界面非常接近(引用Wikipedia's Interface(Java) page):

  

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

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

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

编辑:我注意到even haskell.org admits that they are similar。如果它们如此相似(或者它们是什么?),那么为什么类型类会受到这样的炒作?

更多编辑:哇,这么多很棒的答案!我想我必须让社区决定哪个是最好的。然而,在阅读答案时,所有人似乎都只是说“类节点可以做很多事情,而界面不能或不得不应对泛型”。我不禁想知道,是什么接口可以做什么而类型类不能?另外,我注意到维基百科声称类型类最初是在1989年的论文中发明的*“如何使ad-hoc多态减少广告虽然Haskell仍处于摇篮之中,而Java项目始于1991年并于1995年首次发布。所以可能代替类型类似于接口,而另一种方式,接口受类型类的影响? 是否有任何文件/文件支持或反驳?感谢所有答案,他们都非常有启发性!

感谢所有的投入!

10 个答案:

答案 0 :(得分:38)

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

这意味着使用接口创建add :: t -> t -> t之类的东西真的很难,因为它在多个参数上是多态的,因为接口无法指定方法的参数类型和返回类型是与调用它的对象类型相同的类型(即“自”类型)。使用Generics,有很多方法可以通过创建一个泛型参数的接口来伪造这个,这个参数应该与对象本身的类型相同,就像Comparable<T>这样做的那样,你应该使用{{1} } {} Foo implements Comparable<Foo>类型compareTo(T otherobject)。但是,这仍然需要程序员遵循这个规则,并且当人们想要创建使用该接口的函数时也会引起麻烦,他们必须具有递归泛型类型参数。

另外,你不会有t -> t -> Ordering之类的东西,因为你没有在这里调用函数,所以它不是一种方法。

答案 1 :(得分:36)

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

除此之外,这里有一些值得注意的差异:

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

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

答案 2 :(得分:21)

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

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

这意味着,当您使用将函数foo应用于属于类Foobar的某些类型的参数时,它会查找特定于foo的{​​{1}}实现。那种类型,并使用它。这与C ++ / C#等语言中运算符重载的情况非常相似,除了更灵活和通用之外。

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

另外值得注意的是,类型类具有更灵活的调度方式 - 接口通常仅适用于实现它的单个类,而类型类是为类型定义的,它可以出现在任何地方在类的功能的签名中。 OO接口中的等价物将允许接口定义将该类的对象传递给其他类的方法,定义静态方法和构造函数,这些方法将根据返回类型需要选择实现在调用上下文时,定义接受与实现接口的类相同类型的参数的方法,以及其他根本不能真正转换的内容。

简而言之:它们用于类似的目的,但它们的工作方式有所不同,类型类更具表现力,在某些情况下,由于处理固定类型而不是继承层次结构,因此更易于使用

答案 3 :(得分:11)

我已经阅读了上述答案。我觉得我可以稍微清楚地回答一下:

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

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

答案 4 :(得分:9)

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

答案 5 :(得分:8)

阅读Software Extension and Integration with Type Classes,其中给出了类型类如何解决接口无法解决的许多问题的示例。

论文中列出的例子是:

  • 表达问题,
  • 框架集成问题,
  • 独立可扩展性的问题,
  • 主导分解,散射和纠缠的暴政。

答案 6 :(得分:8)

Master minds of Programming中,有一个关于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

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

答案 7 :(得分:5)

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

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

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

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

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

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

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

答案 8 :(得分:4)

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

这个表通常可以在编译时推断出来。这在Java中可能不那么真实。

但这是功能的表格,而不是方法。方法绑定到一个对象,Haskell类型类不是。

看到它们就像Java的泛型。

答案 9 :(得分:3)

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

另一个不同之处在于你可以使用基于类型的方法,即使你还没有那种类型的具体值!

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

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

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