Rust中的特征与Haskell中的类型类有什么区别?


157

Rust中的特性至少在表面上看起来类似于Haskell中的类型类,但是我看到人们写道它们之间存在一些差异。我确切地想知道这些差异是什么。


8
我对Rust不太了解。但是其他语言中类似技术的常见绊脚石是更高种类的(例如,特征可以超出参数化类型,但不能超出其参数?)和返回类型多态性(例如,特征类型可以出现在函数的结果中,但不在任何地方)在参数中?)。Haskell中前者的一个例子是class Functor f where fmap :: (a -> b) -> (f a -> f b); 后者的一个例子是class Bounded a where maxBound :: a
丹尼尔·瓦格纳

4
GHC还支持多参数类型类(即,涉及多个类型的特征)和功能依赖项,尽管这不是官方Haskell规范的一部分。从链接上建议的Rust语法来看,它一次只能支持一种类型的特征,尽管这种判断也不是基于深厚的经验。
丹尼尔·瓦格纳

4
@DanielWagner存在返回类型的多态性(例如std::default),并且多参数特征排序工作(包括功能依赖性的类似物)起作用,尽管AFAIK仍需要解决特权的第一个参数。但是没有HKT。他们在遥遥无期的愿望清单上,但尚未出现。

4
另一个区别是对孤儿实例的处理。Rust试图对可以在何处写入特质的新印象制定更严格的一致性规则。请参阅此讨论以了解更多详细信息(尤其是此处
Paolo Falabella 2015年

1
Rust目前支持关联的类型和相等性约束,尽管它们不像Haskell的类型族那么强大。它也可以通过特征对象具有存在性类型。
Lambda Fairy

Answers:


61

在基本级别上,没有太大区别,但它们仍然存在。

Haskell将在类型类中定义的函数或值描述为“方法”,就像特征在它们所包围的对象中描述OOP方法一样。但是,Haskell处理这些问题的方式有所不同,将它们视为单个值,而不是像OOP那样将它们固定到一个对象上。这是最明显的表面高度差异。

Rust暂时不能做的一件事是更高级别的输入特征,例如臭名昭​​著的FunctorMonad类型类。

这意味着Rust特征只能描述通常称为“混凝土类型”的特征,换句话说,没有通用参数的特征。Haskell从一开始就可以创建高阶类型类,它们使用的类型类似于高阶函数使用其他函数的方式:使用一个来描述另一个。一段时间以来,在Rust中这是不可能的,但是由于已经实现了相关项,因此这些特征变得司空见惯和惯用。

因此,如果我们忽略扩展名,它们并不完全相同,但是每个扩展名可以近似于另一个扩展名。

如评论中所述,还值得一提的是,GHC(Haskell的主要编译器)支持类型类的其他选项,包括多参数(即涉及的许多类型)类型类和函数依赖项,这是允许进行类型级计算的一个不错的选择。 ,并导致类型家庭。据我所知,Rust既没有funDeps也没有类型族,尽管将来可能没有。†

总而言之,特征和类型类具有根本的差异,这归因于它们之间的交互方式,使它们起作用并最终看起来非常相似。


†可以在此处找到有关Haskell的类型类(包括高类型类型)的一篇不错的文章,并且可以在此处找到有关特征的Rust by Example一章


1
Rust仍然没有任何形式的更高种类的类型。“臭名昭著”需要理由。作为一个概念,函子令人难以置信,无处不在。类型族与关联的类型相同。功能相关性对于关联类型(包括在Haskell中)实质上是多余的。Rust缺少东西。眼底是内射性注解。向后看,Rust的特征和Haskell的类型类在表面上有所不同,但是当您在下面看时,许多差异会消失。尚存的分歧大多是固有的语言在运行不同的领域。
Centril

现在,在许多情况下,关联项都被认为是同义的,对吗?
Vaelus

@Vaelus你是对的,这个答案应该有所更新。现在编辑。
AJFarmar

19

我认为当前的答案忽略了Rust特征与Haskell类型类之间最根本的区别。这些差异与特征与面向对象的语言构造相关的方式有关。有关此信息,请参阅Rust书

  1. 特征声明创建特征类型。这意味着您可以声明这种类型的变量(或更确切地说,该类型的引用)。您还可以将特征类型用作函数,结构字段和类型参数实例化的参数。

    特征引用变量在运行时可以包含不同类型的对象,只要所引用对象的运行时类型实现该特征即可。

    // 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();

    这不是类型类的工作方式。类型类不创建任何类型,因此您不能使用类名声明变量。类型类充当类型参数的界限,但是类型参数必须使用具体类型而不是类型类本身实例化。

    您不能具有实现相同类型类的不同类型的不同事物的列表。(相反,在Haskell中使用存在性类型来表达类似的内容。)注1

  2. 特质方法可以动态调度。这与上一节中描述的内容密切相关。

    动态分配意味着引用指向的对象的运行时类型用于确定通过引用调用的方法。

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

    再次,在Haskell中为此使用了存在性类型。

结论

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

另一方面,Haskell的类型类更高级。Haskell具有例如更高种类的类型和扩展名,例如多参数类型类。


注意1:最新版本的Rust进行了更新,以区分使用特征名称作为类型和使用特征名称作为边界。在特征类型中,名称以dyn关键字为前缀。例如,请参阅此答案以获取更多信息。


2
“类型类不创建任何类型”-我认为最好将其理解dyn Trait为存在性类型的一种形式,因为它们与特征/类型类有关。我们可以考虑dyn将运算符投影到类型上的边界,即dyn : List Bound -> Type。将这个想法带到Haskell,并考虑到“这样就不能使用类名声明变量。”,我们可以在Haskell中间接进行此操作:data Dyn (c :: * -> Constraint) = forall (t :: Type). c t => D t。定义好之后,我们可以使用[D True, D "abc", D 42] :: [D Show]
Centril

8

Rust的“特征”类似于Haskell的类型类。

与Haskell的主要区别在于,特征只对点标记的表达式(即a.foo(b)形式)进行干预。

Haskell类型类扩展到高阶类型。Rust特质仅不支持高阶类型,因为它们在整个语言中都是缺失的,即,这不是特质与类型类之间的哲学差异


1
Rust中的特征不“仅干预点标记的表达式”。例如,考虑Default没有方法,仅与方法无关的功能的特征。
Centril '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.