首页 文章

Java 's Interface and Haskell'的类型类:差异和相似之处?

提问于
浏览
88

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

但是,在the Wikipedia page on type class

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

这似乎与 Java's Interface 相当接近(引用Wikipedia's Interface(Java) page):

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

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

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

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

MORE EDIT: 哇,这么多很棒的答案!我想我必须让社区决定哪个是最好的 . 然而,在阅读答案时,所有人似乎只是说"there are many things typeclass can do while interface cannot or have to cope with generics" . 我不禁想知道,有没有什么 interfaces can do while typeclasses cannot? 此外,我注意到维基百科声称类型类最初发明于1989年的论文* "How to make ad-hoc polymorphism less ad hoc",而Haskell仍处于摇篮之中,而Java项目始于1991年并于1995年首次发布 . 那么 maybe instead of typeclass being similar to interfaces, its the other way around, that interfaces were influenced by typeclass? 有没有文件/论文支持或反驳这一点?感谢所有答案,他们都非常有启发性!

感谢所有的投入!

10 回答

  • 20

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

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

    此外,你不会有像 empty :: t 这样的东西,因为你是一个方法 .

  • 10

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

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

    • 接口方法始终与对象实例相关联 . 换句话说,始终存在隐含的'this'参数,该参数是调用该方法的对象 . 类型类函数的所有输入都是显式的 .

    • 必须将接口实现定义为实现接口的类的一部分 . 相反,类型类'instance'可以完全独立于其关联类型...即使在另一个模块中也是如此 .

    • 类型类允许您为任何已定义的操作定义'default'实现 . 接口只是严格的类型规范,没有实现 .

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

  • 9

    类型类是作为表达"ad-hoc polymorphism"的结构化方式创建的,这基本上是重载的技术术语功能 . 类型类定义如下所示:

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

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

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

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

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

  • 3

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

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

    • Haskell类型类使用"instances"实现,它们与数据类型定义分开 . 在C#/ Java / Scala中,接口/特征必须在类定义中实现 .

    • Haskell类型类允许您返回此类型或自身类型 . Scala特征也可以(this.type) . 请注意,Scala中的"self types"是一个完全不相关的功能 . Java / C#需要使用泛型来处理这种行为 .

    • Haskell类型类允许您在没有输入"this"类型参数的情况下定义函数(包括常量) . Java / C#接口和Scala特征需要在所有函数上使用"this"输入参数 .

    • Haskell类型类允许您定义函数的默认实现 . Scala特性和Java 8接口也是如此 . C#可以使用扩展方法来近似这样的东西 .

  • 4

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

  • 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)
    返回x;
    其他
    回归y;
    }
    与Haskell方法非常相似:min :: Ord a => a - > a - > a
    min x y = if x <y则x else y

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

  • 5

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

    本文中列出的示例包括:

    • 表达问题,

    • 框架集成问题,

    • 独立可扩展性问题,

    • 主导分裂,散乱和纠结的暴政 .

  • 8

    我可以't speak to the 3043452 -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'的操作:

    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); 
        }
    }
    

    它的工作方式是你说'd only need to provide one of those methods to implement the interface. So I'd表示能够在接口级别提供你想要的行为的部分实现是一个区别 .

  • 35

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

    通常可以在编译时推导出该表 . 这在Java中可能不那么真实 .

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

    看它们就像Java的泛型 .

  • 38

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

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

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

    您还可以执行 instance (Read a) => Read [a] where... 之类的操作,它允许您为任何可读事物列表定义读取实例 . 我不太可能在Java中使用't think that' .

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

相关问题