Rustの特性は、少なくとも表面的にはHaskellのタイプクラスに似ているように見えますが、それらの間にはいくつかの違いがあると人々が書いているのを見てきました。私はこれらの違いが何であるかを正確に疑問に思っていました。
std::default
、マルチパラメーター特性の一種の作業(機能的な依存関係の類似物を含む)ですが、特権を持つ最初のパラメーターを回避する必要があります。ただし、HKTはありません。彼らは遠い将来の希望リストに載っていますが、まだ地平線上にはありません。
Rustの特性は、少なくとも表面的にはHaskellのタイプクラスに似ているように見えますが、それらの間にはいくつかの違いがあると人々が書いているのを見てきました。私はこれらの違いが何であるかを正確に疑問に思っていました。
std::default
、マルチパラメーター特性の一種の作業(機能的な依存関係の類似物を含む)ですが、特権を持つ最初のパラメーターを回避する必要があります。ただし、HKTはありません。彼らは遠い将来の希望リストに載っていますが、まだ地平線上にはありません。
回答:
基本的なレベルでは、それほど大きな違いはありませんが、まだ残っています。
Haskellは、型クラスで定義された関数または値を「メソッド」として記述します。特性が、それらが囲むオブジェクトのOOPメソッドを記述します。ただし、Haskellはこれらを異なる方法で処理し、OOPのようにオブジェクトに固定するのではなく、個別の値として扱います。これは、存在する最も明白な表面レベルの違いについてです。
Rustがしばらくできなかったことの1つは、悪名高いクラスや型クラスなどの高次の型付き特性です。Functor
Monad
つまり、Rustの特性は、「コンクリートタイプ」と呼ばれるもの、つまり一般的な引数を持たないもののみを記述できるということです。Haskellは最初から、高次関数が他の関数を使用する方法と同様の型を使用する高次型クラスを作成できます。しばらくの間、Rustではこれは不可能でしたが、関連するアイテムが実装されて以来、そのような特性は一般的で慣用的になっています。
したがって、拡張機能を無視すると、それらはまったく同じではありませんが、それぞれが他の機能と同じように機能します。
コメントで述べたように、GHC(Haskellの主要コンパイラー)が、マルチパラメーター(つまり、多くの型が関与する)型クラスを含む型クラスのさらなるオプション、および関数型の依存関係(型レベルの計算を可能にする素敵なオプション)をサポートすることも言及できます。 、そしてタイプファミリーにつながります。私の知る限り、RustにはfunDepsもタイプファミリーもありませんが、将来的には可能性があります。†
全体として、トレイトとタイプクラスには根本的な違いがあります。それは、それらが相互作用する方法により、それらを機能させ、最終的には非常に似ているように見えます。
†Haskellの型クラス(より高い型のクラスを含む)に関する素晴らしい記事はここにあり、特性に関するRust by Exampleの章はここにあります。
現在の答えは、RustトレイトとHaskell型クラスの最も基本的な違いを見落としていると思います。これらの違いは、特性がオブジェクト指向言語の構成要素に関連する方法に関係しています。これについては、Rustブックを参照してください。
特性宣言は、特性タイプを作成します。つまり、そのような型の変数(または、型の参照)を宣言できます。関数、構造体フィールド、および型パラメーターのインスタンス化のパラメーターとして特性型を使用することもできます。
参照されるオブジェクトのランタイムタイプが特性を実装している限り、実行時に特性参照変数にさまざまなタイプのオブジェクトを含めることができます。
// 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
トレイトメソッドは動的にディスパッチできます。これは、前のセクションで説明したことと強く関連しています。
動的ディスパッチとは、参照が指すオブジェクトの実行時のタイプを使用して、参照を通じて呼び出されるメソッドを決定することを意味します。
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
キーワードが付きます。詳細については、たとえばこの回答を参照してください。
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トレイトは、言語全体から欠落しているため、高次の型のみをサポートしません。つまり、トレイトと型クラスの哲学的な違いではありません。
Default
メソッドを持たず、メソッドに関連付けられていない関数のみを含むトレイトについて考えてみます。
class Functor f where fmap :: (a -> b) -> (f a -> f b)
。後者の例はclass Bounded a where maxBound :: a
です。