Haskellの型クラスとGoのインターフェースに違いがあるかどうか疑問に思っています。型に必要な関数が値に定義されている場合、どちらも関数に基づいて型を定義します。つまり、値は型に一致します。
違いはありますか、これは同じものの2つの名前だけですか?
2つの概念は非常によく似ています。通常のOOP言語では、各オブジェクトにvtable(またはインターフェースの場合:itable)をアタッチします:
_| this
v
+---+---+---+
| V | a | b | the object with fields a, b
+---+---+---+
|
v
+---+---+---+
| o | p | q | the vtable with method slots o(), p(), q()
+---+---+---+
_
これにより、this->vtable.p(this)
と同様のメソッドを呼び出すことができます。
Haskellでは、メソッドテーブルは暗黙の隠し引数に似ています。
_method :: Class a => a -> a -> Int
_
c ++関数のようになります
_template<typename A>
int method(Class<A>*, A*, A*)
_
ここで、_Class<A>
_は、タイプClass
のタイプクラスA
のインスタンスです。メソッドは次のように呼び出されます
_typeclass_instance->p(value_ptr);
_
インスタンスは値とは別です。値はまだ実際のタイプを保持しています。型クラスはいくつかのポリモーフィズムを許可しますが、これはポリモーフィズムのサブタイプ化ではありません。そのため、Class
を満たす値のリストを作成することはできません。例えば。 _instance Class Int ...
_と_instance Class String ...
_があるとすると、_[Class]
_のような、_[42, "foo"]
_のような値を持つ異種リストタイプを作成できません。 (これは、Goアプローチに効果的に切り替わる「既存のタイプ」拡張を使用する場合に可能です)。
Goでは、値は固定された一連のインターフェースを実装していません。その結果、vtableポインターを持つことはできません。代わりに、インターフェイスタイプへのポインターは、データへの1つのポインターとitableへの別のポインターを含むfat pointersとして実装されます。
_ `this` fat pointer
+---+---+
| | |
+---+---+
____/ \_________
v v
+---+---+---+ +---+---+
| o | p | q | | a | b | the data with
+---+---+---+ +---+---+ fields a, b
itable with method
slots o(), p(), q()
this.itable->p(this.data_ptr)
_
Itableは、通常の値からインターフェイス型にキャストするときに、データと結合してファットポインターになります。インターフェース型を取得すると、データの実際の型は無関係になります。実際、メソッドを通過したり、インターフェイスをダウンキャストしたりせずにフィールドに直接アクセスすることはできません(失敗する可能性があります)。
インターフェースディスパッチへのGoのアプローチにはコストがかかります。各ポリモーフィックポインターは通常のポインターの2倍の大きさです。また、あるインターフェースから別のインターフェースへのキャストには、メソッドポインターを新しいvtableにコピーすることが含まれます。しかしitableを作成したら、これにより、従来のOOP=言語が苦労するようなものである)多くのインターフェイスにメソッド呼び出しを安価にディスパッチできます。ここで、mはターゲットインターフェイスのメソッドの数です、およびbは基本クラスの数です。
メソッドのディスパッチは通常キャッシュされるため、メソッドディスパッチの一般的な複雑さははるかに優れていますが、最悪の場合の複雑さは非常に恐ろしいものです。
比較すると、GoにはO(1)またはO(m)アップキャスト、およびO(1)メソッドディスパッチがあります。 Haskellにはアップキャストがなく(型クラスで型を制約することはコンパイル時の効果です)、O(1)メソッドディスパッチします。
いくつかの違いがあります
Maybe
はMonad
であることを宣言する必要があります。 Goインターフェースは構造的に型付けされています。circle
がarea() float64
を宣言し、square
も宣言する場合、どちらも自動的にインターフェースshape
の下にあります。Maybe a
の例のように)より高い種類の型の型クラスがあります。 Goにはこれらに相当するものはありません。+ :: Num a => a -> a -> a
は、Goでは表現できません。それらは完全に異なります。 Goインターフェイスは値のプロトコルを定義し、Haskellタイプクラスはタイプのプロトコルを定義します。 (そのため、結局、これらは「タイプクラス」と呼ばれます。値を分類するOOクラス(またはGoのインターフェース)とは異なり、タイプクラスを分類します。)
Goインターフェースは、古い構造的な型付けを退屈にするだけで、それ以上のものはありません。