Rust中的特质和Haskell中的类型类之间有什么区别?

209

9
我对 Rust 并不了解太多。但是其他语言中类似技术的常见障碍是高级类型(例如:特征能否适用于参数化类型,但不能适用于它们的参数?),以及返回类型多态性(例如:特征类型是否可以出现在函数结果中,但不能在任何参数中出现?)。Haskell 中前者的一个示例是 class Functor f where fmap :: (a -> b) -> (f a -> f b);后者的一个示例是 class Bounded a where maxBound :: a - Daniel Wagner
4
GHC 支持多参数类型类(即涉及多个类型的特性)和函数依赖,但这不是官方 Haskell 规范的一部分。从您提供的 Rust 语法链接可以看出,它只能支持一次跨越一个类型的特性,尽管这种判断也并非基于深入的经验。 - Daniel Wagner
4
@DanielWagner 存在返回类型多态性(例如 std::default),并且多参数特征有点可行(包括函数依赖的类比),尽管据我所知,需要解决第一个参数受到特殊待遇的问题。但是没有高阶类型。它们在远期愿望清单上,但目前还没有出现在视野中。 - user395760
4
另一个区别是对孤儿实例的处理方式。Rust试图在哪里编写一个新的Trait impl上拥有更严格的一致性规则。请参见此讨论以获取更多详细信息(特别是此处)。 - Paolo Falabella
1
Rust现在支持关联类型和相等性约束,尽管它们不像Haskell的类型族那样强大。它还通过trait对象具有存在类型。 - Lambda Fairy
显示剩余2条评论
4个回答

82
在基本层面上,它们没有太大的区别,但它们仍然存在。
Haskell将类型类中定义的函数或值描述为“方法”,就像特征描述封装对象中的OOP方法一样。然而,Haskell以不同的方式处理它们,将它们视为单独的值,而不是像OOP那样将它们固定到对象上。这是最显而易见的表面差异。
Rust有一段时间无法实现高阶类型特征,例如臭名昭著的FunctorMonad类型类。
这意味着Rust特征只能描述通常称为“具体类型”的内容,换句话说,没有泛型参数的类型。从一开始,Haskell就可以创建使用类似于高阶函数使用其他函数的类型的高阶类型类:使用一个类型来描述另一个类型。在一段时间内,这在Rust中是不可能的,但自从关联项被实现以来,这种特征已经变得非常普遍和惯用。
因此,如果我们忽略扩展,它们并不完全相同,但每个语言都可以近似地实现对方的功能。

值得一提的是,正如评论中所说,GHC(Haskell的主要编译器)支持更多的类型类选项,包括多参数(即涉及许多类型)类型类,以及功能依赖,这是一个很棒的选项,允许进行类型级计算,并引出类型族。据我所知,Rust 没有 funDeps 或类型族,但它可能会在未来加入†。

总之,特质和类型类有根本性的区别,由于它们的交互方式,使它们在最终的表现和行为上看起来非常相似。


在这里可以找到一篇关于Haskell类型类的好文章(包括高级类型),链接,Rust by Example中有关特性的章节可以在此处找到。


6
Rust仍没有任何形式的高阶类型。“臭名昭著”需要解释。函数子是一个非常普遍和有用的概念。类型族与关联类型相同。函数依赖性本质上与关联类型(包括Haskell中的)重复。就函数依赖而言,Rust所缺少的是注入性注释。你弄反了,Rust的特征和Haskell的类型类在表面上是不同的,但是当你深入了解时,许多差异会消失。剩下的差异大多数是由于语言所操作的不同领域固有的。 - Centril
相关项目现在在许多情况下被认为是惯用语了,对吧? - Vaelus
@Vaelus 你是对的- 这个答案应该稍微更新一下。我正在编辑。 - AJF
这个答案在高阶类型方面仍然存在错误,您所称的高阶特质;我们仍然无法在Rust中创建Functor或Monad特质,这不仅是历史限制。 - Zoey Hewll
@ZoeyHewll 当然我们可以创建一个Functor和Monad。Rust通过泛型关联类型支持更高级的种类。https://github.com/mtomassoli/HKTs - Piotr Kołaczkowski

32

我认为当前的答案忽略了Rust traits和Haskell type classes之间最基本的区别。这些差异与traits与面向对象语言构造的关系有关。有关此信息,请参见Rust book

  1. A trait declaration creates a trait type. This means that you can declare variables of such a type (or rather, references of the type). You can also use trait types as parameters on function, struct fields and type parameter instantiations.

    A trait reference variable can at runtime contain objects of different types, as long as the runtime type of the referenced object implements the trait.

    // The shape variable might contain a Square or a Circle, 
    // we don't know until runtime
    let shape: &Shape = get_unknown_shape();
    
    // Might contain different kinds of shapes at the same time
    let shapes: Vec<&Shape> = get_shapes();
    

    This is not how type classes work. Type classes create no types, so you can't declare variables with the class name. Type classes act as bounds on type parameters, but the type parameters must be instantiated with a concrete type, not the type class itself.

    You can not have a list of different things of different types which implement the same type class. (Instead, existential types are used in Haskell to express a similar thing.) Note 1

  2. Trait methods can be dynamically dispatched. This is strongly related to the things that are described in the section above.

    Dynamic dispatch means that the runtime type of the object a reference points is used to determine which method that is called though the reference.

    let shape: &Shape = get_unknown_shape();
    
    // This calls a method, which might be Square.area or
    // Circle.area depending on the runtime type of shape
    print!("Area: {}", shape.area());
    

    Again, existential types are used for this in Haskell.

结论

在我看来,特质在许多方面与类型类相同。此外,它们具有面向对象接口的功能。

另一方面,Haskell的类型类更加先进。例如,Haskell拥有更高级别的类型和扩展,如多参数类型类。


注1:Rust的最新版本有一个更新,以区分特质名称作为类型和特质名称作为约束条件的用法。在特质类型中,名称前缀为dyn关键字。有关更多信息,请参见此答案


3
“Type classes create no types”可以理解为类型类并不创建类型。在了解特质/类型类相关内容时,最好把dyn Trait视为一种存在类型的形式。我们可以将dyn视为一个约束的运算符,用于将其投射到类型上,即dyn:List Bound -> Type。在Haskell中,考虑到“因此您无法使用类名称声明变量”,我们可以通过以下方式间接完成:data Dyn (c :: * -> Constraint) = forall (t :: Type). c t => D t。定义完这个之后,我们就可以使用[D True, D "abc", D 42] :: [D Show]进行操作了。 - Centril
@Lii 所以 traits 是类型类,它们恰好也隐式地定义了存在类型?这并没有太大的区别。 - YoTengoUnLCD

10

Rust的“traits”类似于Haskell的类型类。

与Haskell的主要区别在于,traits仅对带有点符号表示法的表达式起作用,即形式为a.foo(b)的表达式。

Haskell类型类扩展到高阶类型。 Rust traits之所以不支持高阶类型,是因为整个语言中缺少它们,即这不是traits和类型类之间的哲学差异。


6
Rust中的特质不仅仅适用于带有点符号表示法的表达式。例如,考虑Default特质,它没有方法,只有非方法关联函数。 - Centril

0

Rust的特质类似于Haskell的类型类。它们是将相似函数分组的一种方式。

Haskell和其他编程语言之间的主要区别在于,特质只适用于使用点符号表示的表达式,例如a.foo(b)。

在Haskell中,类型类可以扩展到高阶类型。这意味着您可以通过使用特质创建像语言中的其他类型一样行为的类型。Rust没有此功能,因为它没有高阶类型。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接