ChatGPT解决这个技术问题 Extra ChatGPT

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

Rust 中的 Traits 至少在表面上与 Haskell 中的 typeclasses 相似,但是我看到人们写到它们之间存在一些差异。我想知道这些差异到底是什么。

我对 Rust 了解不多。但是其他语言中类似技术的常见绊脚石是更高的种类(例如,特征可以在参数化类型上,但不是它们的参数?)和返回类型多态性(例如,特征类型可以出现在函数的结果中,但不能出现在任何地方在争论中?)。 Haskell 中前者的一个例子是 class Functor f where fmap :: (a -> b) -> (f a -> f b);后者的一个例子是 class Bounded a where maxBound :: a
GHC 还支持多参数类型类(即涉及多种类型的特征)和函数依赖关系,尽管这不是官方 Haskell 规范的一部分。从链接中建议的 Rust 语法来看,它一次只能支持一种类型的特征,尽管这种判断又不是基于深厚的经验。
@DanielWagner 存在返回类型多态性(例如 std::default),并且多参数特征排序工作(包括功能依赖的类似物),尽管 AFAIK 需要解决第一个特权参数。但是没有香港电讯。他们在遥远的未来愿望清单上,但还没有出现。
另一个区别是孤儿实例的处理。 Rust 试图对可以在何处编写 trait 的新 impl 制定更严格的连贯性规则。有关详细信息,请参阅 this discussion(尤其是 here
Rust 现在支持 associated types and equality constraints,尽管它们不如 Haskell 的类型族强大。它还具有通过 trait objects 的存在类型。

A
AJF

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

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

Rust 暂时无法做的一件事是高阶类型特征,例如臭名昭著的 FunctorMonad 类型类。

这意味着 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 缺少的东西。 fundeps 是注入性注释。你反过来说,Rust 的特征和 Haskell 的类型类在表面上是不同的,但是当你往下看时,许多差异就会消失。仍然存在的差异主要是语言运行的不同领域所固有的。
关联项目现在在许多情况下都被认为是惯用的,对吧?
@Vaelus你是对的 - 这个答案应该更新一点。现在编辑。
这个答案对于更高种类的类型仍然是错误的,你称之为更高阶的特征;我们仍然无法在 rust 中制作 Functor 或 Monad 特征,这不仅仅是一个历史限制。
L
Lii

我认为当前的答案忽略了 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]
@Lii 所以特征是类型类,也恰好隐式定义了存在类型?那不是很大的区别。
A
Anuj Gupta

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

与 Haskell 的主要区别在于,特征只干预带有点符号的表达式,即 a.foo(b) 的形式。

Haskell 类型类扩展到高阶类型。 Rust trait 仅不支持高阶类型,因为它们在整个语言中都缺失了,也就是说,这不是 trait 和类型类之间的哲学区别


Rust 中的特征不“只干预带有点符号的表达式”。例如,考虑没有方法的 Default 特征,只有与方法无关的函数。