コンピュータサイエンスにおいて、型クラスは、プログラミング言語におけるアドホックな多相性をサポートする型システム構成要素です。これは、パラメトリック多相性型の型変数に制約を追加することによって実現されます。このような制約は通常、型クラスと型変数を含み、 は、に関連付けられたオーバーロードされた操作をサポートするメンバーを持つ型にのみインスタンス化できることを意味します。TaaT
型クラスは、Philip WadlerとStephen BlottによってStandard MLの拡張として最初に提案された後、Haskell言語で初めて実装されました。 [ 1 ] [ 2 ]当初は、オーバーロードされた算術演算子と等価演算子を原則的な方法で実装する方法として考えられていました。 [ 3 ] [ 2 ] Standard ML の「eqtypes」とは対照的に、Haskell で型クラスを使用して等価演算子をオーバーロードする場合、コンパイラのフロントエンドや基盤となる型システム の大幅な変更は必要ありません。 [ 4 ]eqtypes
型クラスは、そのクラスに属するすべての型に対して存在しなければならない、関数名または定数名とその型のセットを指定することによって定義されます。Haskellでは、型はパラメータ化できます。Eq等価性を許容する型を含むことを目的とした型クラスは、次のように宣言されます。
class Eq a where ( == ) :: a -> a -> Bool ( /= ) :: a -> a -> Boolここでa、 は型クラスのインスタンスでありEq、 はa2 つの関数 (等価関数と不等価関数) の関数シグネチャを定義します。これらの関数はそれぞれ 型の 2 つの引数を取りa、ブール値を返します。
型変数は種類をa持つ(Typeは、最新のグラスゴーハスケルコンパイラ(GHC)リリースでは、としても知られています。 [ 5 ]これは、の種類Eqが
Eq ::型->制約この宣言は、「適切な型の、およびという名前の関数が定義されている場合、その型aは型クラスに属する」と解釈できます。プログラマーは、次のように関数(要素がリストに含まれているかどうかを判断する関数)を定義できます。Eq(==)(/=)elem
elem :: Eq a => a -> [ a ] -> Bool elem y [] = False elem y ( x : xs ) = ( x == y ) ||要素y xsこの関数は、コンテキストを持つelem型を持ち、型クラスに属する型のみを対象とするように制約されます。(Haskellでは、これは「クラス制約」と呼ぶことができます。)a -> [a] -> BoolEq aaaEq=>
任意の型は、指定された型に対するのすべてのメソッドの実装を定義するインスタンス宣言を使用することで、t指定された型クラスのメンバーにすることができます。たとえば、新しいデータ型が定義された場合、この新しい型は、型の値に対する等価関数を任意の有用な方法で提供することで、のインスタンスにすることができます。これが完了すると、関数は、つまり型の要素のリストに対して使用できます。CCttEqtelem[t]t
型クラスは、オブジェクト指向プログラミング言語のクラスとは異なります。具体的には、は型ではありません。型の値は存在しません。EqEq
型クラスはパラメトリック多相性と密接に関連しています。たとえば、上記の型は、型クラス制約「 」がなければelemパラメトリック多相性型になります。a -> [a] -> BoolEq a =>
型クラスは、特定の型Typeの型変数を必ずしも取る必要はなく、任意の型の型変数を取ることができます。このような高次の型を持つ型クラスは、コンストラクタクラスと呼ばれることがあります(ここで言及されるコンストラクタはMaybe、のようなデータコンストラクタではなく、のような型コンストラクタですJust)。例として、Monadクラス があります。
class Monad m where return :: a -> m a ( >>= ) :: m a -> ( a -> m b ) -> m bこれmは型変数に適用され、その型が 型であることを示します。Type -> Typeつまり、型を受け取り、型を返します。したがって、 の型はMonad次のようになります。
モナド:: (型->型) ->制約型クラスは複数の型パラメータを許容するため、型クラスは型に関する関係と見なすことができます。[ 6 ]例えば、GHC標準ライブラリでは、クラスはIArray一般的な不変配列インターフェースを表します。このクラスでは、型クラス制約IArray a eにより、aは型の要素を含む配列型となります。(この多相性の制約は、例えば、ボックス化されていないe配列型を実装するために使用されます。)
マルチメソッドと同様に、マルチパラメータ型クラスは、複数の引数の型、そして実際には戻り値の型に応じて、メソッドの異なる実装を呼び出すことをサポートします。マルチパラメータ型クラスは、実行時に呼び出しごとに呼び出すメソッドを検索する必要はありません。[ 7 ] : minute 25:12むしろ、呼び出すメソッドは、単一パラメータ型クラスと同様に、最初にコンパイルされ、型クラスインスタンスの辞書に格納されます。
マルチパラメータ型クラスを使用するHaskellコードは、最新の標準規格ではないHaskell 98規格の時点では移植性がありません。人気の高いHaskell実装であるGHCとHugsは、マルチパラメータ型クラスをサポートしています。
Haskell では、型クラスが改良され、プログラマが型パラメータ間の関数従属性を宣言できるようになりました。これは、関係データベース理論から着想を得た概念です。[ 8 ] [ 9 ]つまり、プログラマは、型パラメータのサブセットの特定の割り当てによって、残りの型パラメータが一意に決定されることを主張できます。たとえば、型の状態パラメータを持つ一般的なモナド は、型クラス制約を満たします。この制約には、関数従属性があります。これは、型クラスの特定のモナドに対して、からアクセス可能な状態型が一意に決定されることを意味します。これは、コンパイラの型推論に役立つだけでなく、プログラマの型指向プログラミングにも役立ちます。msMonad.State s mm -> smMonad.Statem
サイモン・ペイトン・ジョーンズは、複雑性の観点から、Haskellにおける関数従属性の導入に反対している。[ 10 ]
型クラスと暗黙のパラメータは、性質上非常に似ていますが、完全に同じではありません。型クラス制約を持つ多相関数は次のようになります。
sum :: Num a => [ a ] -> aは、暗黙的にインスタンスを受け入れる関数として直感的に扱うことができますNum。
sum_ :: Num_ a -> [ a ] -> aインスタンスNum_ aは基本的に、のインスタンス定義を含むレコードですNum a。(実際、グラスゴーHaskellコンパイラでは、内部的に型クラスはこのように実装されています。)
しかし、重要な違いがあります。暗黙のパラメータはより柔軟で、異なるインスタンスをNum Int渡すことができます。対照的に、型クラスは、いわゆる一貫性プロパティを強制します。これは、任意の型に対してインスタンスの選択肢が1つだけ存在する必要があることを要求します。一貫性プロパティにより、型クラスはやや反モジュール的になり、そのため、孤立インスタンス(クラスも対象となる型も含まないモジュールで定義されたインスタンス)は強く推奨されません。しかし、一貫性は言語にもう1つのレベルの安全性を追加し、同じコードの2つの独立した部分が同じインスタンスを共有することを保証します。[ 11 ]
例えば、順序付きセット(型Set a)が機能するには、要素(型)に全順序付けが必要です。これは、要素に対する比較演算子を定義する制約によって証明できます。しかし、全順序付けを課す方法は数多く存在します。セットアルゴリズムは、セットが構築された後に順序が変更されると一般的に許容しないため、セットを操作する関数に互換性のないインスタンスを渡すと、誤った結果(またはクラッシュ)につながる可能性があります。したがって、この特定のシナリオでは、の一貫性を強制することが非常に重要です。aOrd aOrd aOrd a
Scala型クラスにおけるインスタンス (または「辞書」) は、完全に独立した種類のエンティティではなく、言語における通常の値です。[ 12 ] [ 13 ]これらのインスタンスは、デフォルトでは、明示的に宣言された暗黙の仮引数の暗黙の引数として使用されるスコープ内の適切なインスタンスを見つけることによって提供されますが、通常の値であるということは、曖昧さを解消するために明示的に提供できることを意味します。結果として、Scala 型クラスは一貫性プロパティを満たさず、実質的には暗黙の引数の構文糖衣となります。
これはCatsのドキュメントから抜粋した例です。[ 14 ]
// テキスト表現を提供する型クラスtrait Show [ A ] { def show ( f : A ): String }// Show[A] の暗黙的なインスタンスが利用可能な場合にのみ機能する多相関数def log [ A ]( a : A )( implicit s : Show [ A ]) = println ( s . show ( a ))// String のインスタンスimplicit val stringShow = new Show [ String ] { def show ( s : String ) = s }// パラメータ stringShow はコンパイラによって挿入されました。scala > log ( "a string" ) a stringRocq(旧称Coq)バージョン8.2以降は、適切なインスタンスを推論することで型クラスもサポートしています。[ 15 ] Agda 2の最近のバージョンも、「インスタンス引数」と呼ばれる同様の機能を提供しています。[ 16 ]
Standard MLでは、「等価型」の仕組みはおおよそ Haskell の組み込み型クラスに対応しますEqが、すべての等価演算子はコンパイラによって自動的に導出されます。プログラマが制御できるのは、構造体内のどの型コンポーネントが等価型であるか、そして多相型範囲内のどの型変数が等価型であるかを指定することに限られます。
SML とOCamlのモジュールとファンクタは、Haskell の型クラスと同様の役割を果たすことができます。主な違いは型推論の役割であり、これにより型クラスはアドホックな多相性に適しています。[ 17 ] OCaml のオブジェクト指向サブセットは、型クラスのアプローチとある程度比較できるもう 1 つのアプローチです。
オーバーロードされたデータに対する類似の概念(GHCで実装されている)は、型ファミリーである。[ 18 ]
C++、特にC++20では、「概念」を用いた型クラスのサポートが提供されています。例として、前述のHaskellにおける型クラスの例は、Eq以下のように実装されます。
using std :: convertible_to ;// std::equality_comparable と同等のテンプレート< typename T > concept EqualityComparable = requires ( T a , T b ) { { a == b } -> convertible_to < bool > ; { a != b } -> convertible_to < bool > ; };// コンセプト EqualityComparable を使用: template < EqualityComparable T > [[ nodiscard ]] constexpr bool isEqual ( const T & x , const T & y ) noexcept { return x == y ; }Go言語では、インターフェースは型クラスと見なすことができ、C++の概念とほぼ同等である。[ 19 ]
JavaとC#では、インターフェースは型クラスに似ており、実装するメソッドの「インターフェース」を定義します(Javaインターフェースを参照)。
Clean の型クラスは Haskell に似ていますが、構文が少し異なります。
Rustは、一貫性のある型クラスの限定された形式であるトレイトをサポートしており、インターフェースに似ていると見なすこともできます。[ 20 ]
Mercuryには型クラスがありますが、Haskellの型クラスと全く同じではありません。
Scalaでは、型クラスは暗黙のパラメータなどの既存の言語機能で実装できるプログラミングイディオムであり、それ自体が独立した言語機能ではありません。Scalaでの実装方法のおかげで、曖昧さが生じた場合に、コード内の特定の場所で型に対してどの型クラスインスタンスを使用するかを明示的に指定することが可能です。ただし、曖昧な型クラスインスタンスはエラーが発生しやすいため、これは必ずしも利点とは言えません。
証明支援システムRocqは、最近のバージョンで型クラスもサポートするようになりました。通常のプログラミング言語とは異なり、Rocqでは、型クラスの定義内で記述された型クラスの法則(モナド法則など)は、使用する前に各型クラスのインスタンスについて数学的に証明する必要があります。
TypeData.Kind