在Haskell中解释类型类 我是一个C++ java程序员,我日常编程中使用的主要范例是OOP。在一些帖子中,我读到一条评论,认为类型类在本质上比OOP更直观。有人能用简单的话解释一下类型类的概念吗?这样像我这样的OOP人员就可以理解它了。

在Haskell中解释类型类 我是一个C++ java程序员,我日常编程中使用的主要范例是OOP。在一些帖子中,我读到一条评论,认为类型类在本质上比OOP更直观。有人能用简单的话解释一下类型类的概念吗?这样像我这样的OOP人员就可以理解它了。,java,c++,oop,haskell,functional-programming,Java,C++,Oop,Haskell,Functional Programming,类型类可以比作“实现”接口的概念。如果Haskell中的某些数据类型实现了“Show”接口,那么它可以与所有需要“Show”对象的函数一起使用。那么,简短的版本是:类型类是Haskell用于特殊多态性的。 …但这可能并没有为你澄清任何事情 对于OOP背景的人来说,多态性应该是一个熟悉的概念。然而,这里的关键点是参数多态性和特殊多态性之间的区别 参数化多态性指在结构类型上操作的函数,该结构类型本身由其他类型(如值列表)参数化。参数多态性在Haskell中几乎无处不在;C和Java称之为“泛型”。基

类型类可以比作“实现”接口的概念。如果Haskell中的某些数据类型实现了“Show”接口,那么它可以与所有需要“Show”对象的函数一起使用。

那么,简短的版本是:类型类是Haskell用于特殊多态性的。

…但这可能并没有为你澄清任何事情

对于OOP背景的人来说,多态性应该是一个熟悉的概念。然而,这里的关键点是参数多态性和特殊多态性之间的区别

参数化多态性指在结构类型上操作的函数,该结构类型本身由其他类型(如值列表)参数化。参数多态性在Haskell中几乎无处不在;C和Java称之为“泛型”。基本上,泛型函数对特定结构执行相同的操作,无论类型参数是什么

特殊多态性,另一方面,意味着一组不同的功能,根据类型做不同(但概念上相关)的事情。与参数多态性不同,需要为可与之一起使用的每种可能类型分别指定特殊多态性函数因此,即席多态性是其他语言中的各种功能的通用术语,例如C/C++中的函数重载或OOP中基于类的分派多态性。

Haskell的类型类相对于其他形式的特殊多态性的一个主要卖点是更大的灵活性,因为允许在类型签名中的任何位置使用多态性。例如,大多数语言不会根据返回类型区分重载函数;类型类可以

许多OOP语言中的接口与Haskell的类型类有些相似——您可以指定一组要以特殊多态方式处理的函数名/签名,然后明确描述各种类型如何与这些函数一起使用。Haskell的类型类的使用方式与此类似,但具有更大的灵活性:您可以为类型类函数编写任意类型签名,用于实例选择的类型变量显示在您喜欢的任何位置,而不仅仅是作为调用方法的对象的类型

一些Haskell编译器——包括最流行的GHC——提供了使类型类更加强大的语言扩展,例如多参数类型类,它允许您基于多种类型执行特殊的多态函数分派(类似于OOP中的“多重分派”)


为了给您一点它的味道,这里有一些模糊的Java/C风格的伪代码:

interface IApplicative<>
{
    IApplicative<T> Pure<T>(T item);
    IApplicative<U> Map<T, U>(Function<T, U> mapFunc, IApplicative<T> source);
    IApplicative<U> Apply<T, U>(IApplicative<Function<T, U>> apFunc, IApplicative<T> source);
}

interface IReducible<>
{
    U Reduce<T,U>(Function<T, U, U> reduceFunc, U seed, IReducible<T> source);
}
接口i应用程序
{
i应用纯(T项);
i应用程序映射(函数mapFunc,i应用程序源);
i应用程序应用(i应用程序apFunc,i应用程序源);
}
界面不可约
{
U Reduce(函数reduceFunc,U seed,i可还原源);
}

请注意,我们正在定义泛型类型上的接口,并定义一个方法,其中接口类型仅显示为返回类型,
Pure
。不明显的是,接口名称的每一次使用都应该表示相同的类型(即,不混合实现接口的不同类型),但我不确定如何表达这一点。

除了xtofl和camccann在其优秀答案中所写的内容之外,在比较Java接口和Haskell类型类时,需要注意以下几点:

  • Java接口是封闭的,这意味着任何给定类实现的接口集在何时何地定义时都会一劳永逸地确定

  • Haskell的类型类是开放的,这意味着任何类型(或多参数类型类的类型组)都可以在任何时候成为任何类型类的成员,只要可以为类型类定义的函数提供合适的定义

  • 类型类(和Clojure的协议非常相似)的开放性是一个非常有用的特性;Haskell程序员通常会提出一个新的抽象,并通过巧妙地使用类型类立即将其应用于一系列涉及现有类型的问题。

    在C++/etc中,“虚拟方法”是根据
    这个
    /
    自身
    隐式参数的类型进行调度的。(该方法在对象隐式指向的函数表中被指向)

    类型类的工作方式不同,可以做“接口”所能做的一切,甚至更多。让我们从一个简单的接口不能做的事情开始:Haskell的
    Read
    type类

    ghci> -- this is a Haskell comment, like using "//" in C++
    ghci> -- and ghci is an interactive Haskell shell
    ghci> 3 + read "5" -- Haskell syntax is different, in C: 3 + read("5")
    8
    ghci> sum (read "[3, 5]") -- [3, 5] is a list containing 3 and 5
    8
    ghci> -- let us find out the type of "read"
    ghci> :t read
    read :: (Read a) => String -> a
    
    read
    的类型是
    (reada)=>String->a
    ,这意味着对于实现
    read
    type类的每个类型,
    read
    都可以将
    字符串
    转换为该类型。这是基于返回类型的分派,不可能使用“接口”

    <>这不能用C++的方法来实现,函数表是从对象中检索的,在这里,你甚至没有相关的对象,直到代码> >读/代码>返回它,你怎么称呼它? 与允许这种情况发生的接口相比,实现的一个关键区别在于函数表没有指向对象内部,而是由编译器单独传递给被调用的函数

    此外,在C++/etc中,当定义一个类时,它们还负责实现它们的接口。这就意味着你不能
    sum :: Num a => [a] -> a
    
    sum :: [Num] -> Num