web-dev-qa-db-ja.com

Haskellの型クラスとGoのインターフェースの違いは何ですか?

Haskellの型クラスとGoのインターフェースに違いがあるかどうか疑問に思っています。型に必要な関数が値に定義されている場合、どちらも関数に基づいて型を定義します。つまり、値は型に一致します。

違いはありますか、これは同じものの2つの名前だけですか?

8
ceving

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は基本クラスの数です。

  • C++は、オブジェクトのスライスを行うか、キャスト時に仮想継承ポインターを追跡する必要がありますが、単純なvtableアクセスを持っています。 O(1)またはO(b)アップキャストのコスト、ただしO(1)メソッドのディスパッチ。
  • Java Hotspot VMはアップキャスト時に何もする必要はありませんが、インターフェースメソッドの検索時に、そのクラスによって実装されたすべてのitablesを線形検索します。= O(1)アップキャストですが、O(b)メソッドディスパッチです。
  • Pythonはアップキャスト時に何もする必要はありませんが、C3線形化された基本クラスリストによる線形検索を使用します。 O(1)アップキャストですが、O(b²)メソッドはディスパッチしますか?C3のアルゴリズムの複雑さはわかりません。
  • .NET CLRはHotspotと同様のアプローチを使用しますが、メモリ使用量を最適化するために別のレベルの間接参照を追加します。 O(1)アップキャストですが、O(b)メソッドディスパッチです。

メソッドのディスパッチは通常キャッシュされるため、メソッドディスパッチの一般的な複雑さははるかに優れていますが、最悪の場合の複雑さは非常に恐ろしいものです。

比較すると、GoにはO(1)またはO(m)アップキャスト、およびO(1)メソッドディスパッチがあります。 Haskellにはアップキャストがなく(型クラスで型を制約することはコンパイル時の効果です)、O(1)メソッドディスパッチします。

6
amon

いくつかの違いがあります

  1. Haskellの型クラスは主に型付けされています-MaybeMonadであることを宣言する必要があります。 Goインターフェースは構造的に型付けされています。circlearea() float64を宣言し、squareも宣言する場合、どちらも自動的にインターフェースshapeの下にあります。
  2. Haskell(GHC拡張を含む)にはMulti Parameter Type Classesと(私のMaybe aの例のように)より高い種類の型の型クラスがあります。 Goにはこれらに相当するものはありません。
  3. Haskellでは、型クラスはバインドされたポリモーフィズムで消費され、Goでは表現できない制約が与えられます。たとえば、浮動小数点と四元数を追加しようとしないことを保証する+ :: Num a => a -> a -> aは、Goでは表現できません。
7
walpen

それらは完全に異なります。 Goインターフェイスは値のプロトコルを定義し、Haskellタイプクラスはタイプのプロトコルを定義します。 (そのため、結局、これらは「タイプクラス」と呼ばれます。値を分類するOOクラス(またはGoのインターフェース)とは異なり、タイプクラスを分類します。)

Goインターフェースは、古い構造的な型付けを退屈にするだけで、それ以上のものはありません。

4
Jörg W Mittag