Rust 中的 Traits 至少在表面上与 Haskell 中的 typeclasses 相似,但是我看到人们写到它们之间存在一些差异。我想知道这些差异到底是什么。
class Functor f where fmap :: (a -> b) -> (f a -> f b)
;后者的一个例子是 class Bounded a where maxBound :: a
。
std::default
),并且多参数特征排序工作(包括功能依赖的类似物),尽管 AFAIK 需要解决第一个特权参数。但是没有香港电讯。他们在遥远的未来愿望清单上,但还没有出现。
在基本层面上,没有太大区别,但它们仍然存在。
Haskell 将类型类中定义的函数或值描述为“方法”,就像特征描述它们所包含的对象中的 OOP 方法一样。然而,Haskell 以不同的方式处理这些,将它们视为单独的值,而不是像 OOP 那样将它们固定到一个对象上。这是最明显的表面水平差异。
Rust 暂时无法做的一件事是高阶类型特征,例如臭名昭著的 Functor
和 Monad
类型类。
这意味着 Rust 特征只能描述通常称为“具体类型”的东西,换句话说,就是没有泛型参数的东西。 Haskell 从一开始就可以创建高阶类型类,其使用类型类似于高阶函数使用其他函数的方式:使用一个来描述另一个。有一段时间,这在 Rust 中是不可能的,但自从 associated items 已经实现,这样的特征已经变得司空见惯和惯用语。
因此,如果我们忽略扩展,它们并不完全相同,但每个都可以近似于另一个可以做的事情。
正如评论中所说,还值得一提的是,GHC(Haskell 的主要编译器)支持类型类的更多选项,包括 multi-parameter(即涉及许多类型)类型类和 functional dependencies,这是一个允许类型级别的可爱选项计算,并导致 type families。据我所知,Rust 既没有 funDeps 也没有类型族,尽管将来可能会。†
总而言之,traits 和 typeclass 有根本的区别,由于它们交互的方式,使它们最终表现得非常相似。
† 一篇关于 Haskell 类型类(包括更高类型的)的好文章可以在 here 中找到,关于特性的 Rust by Example 章节可以在 here 中找到
我认为当前的答案忽略了 Rust 特征和 Haskell 类型类之间最根本的区别。这些差异与特征与面向对象语言结构相关的方式有关。有关这方面的信息,请参阅 Rust book。
一个 trait 声明创建一个 trait 类型。这意味着您可以声明这种类型的变量(或者更确切地说,该类型的引用)。您还可以将特征类型用作函数、结构字段和类型参数实例化的参数。特征引用变量在运行时可以包含不同类型的对象,只要被引用对象的运行时类型实现了特征。 // shape 变量可能包含 Square 或 Circle, // 直到运行时我们才知道 let shape: &Shape = get_unknown_shape(); // 可能同时包含不同种类的形状 let shape: Vec<&Shape> = get_shapes();这不是类型类的工作方式。类型类不创建类型,因此您不能使用类名声明变量。类型类充当类型参数的界限,但类型参数必须用具体类型实例化,而不是类型类本身。您不能拥有实现相同类型类的不同类型的不同事物的列表。 (相反,在 Haskell 中使用存在类型来表达类似的东西。) 注 1 Trait 方法可以动态分派。这与上一节中描述的内容密切相关。动态分派是指使用引用指向的对象的运行时类型来确定通过引用调用哪个方法。让形状:&Shape = get_unknown_shape(); // 这会调用一个方法,该方法可能是 Square.area 或 // Circle.area,具体取决于 shape print 的运行时类型!("Area: {}", shape.area());同样,在 Haskell 中,存在类型用于此目的。
综上所述
在我看来,特征在许多方面与类型类的概念相同。此外,它们还具有面向对象接口的功能。
另一方面,Haskell 的类型类更高级。 Haskell 具有例如更高种类的类型和扩展,例如多参数类型类。
注意 1:Rust 的最新版本进行了更新,以区分特征名称作为类型的使用和特征名称作为边界的使用。在特征类型中,名称以 dyn
关键字为前缀。有关详细信息,请参阅此 answer。
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]
。
Rust 的“特征”类似于 Haskell 的类型类。
与 Haskell 的主要区别在于,特征只干预带有点符号的表达式,即 a.foo(b) 的形式。
Haskell 类型类扩展到高阶类型。 Rust trait 仅不支持高阶类型,因为它们在整个语言中都缺失了,也就是说,这不是 trait 和类型类之间的哲学区别
Default
特征,只有与方法无关的函数。