JavaのインターフェースとHaskellの型クラス:違いと類似点?


111

Haskellを学んでいると、その型クラスに気づきました。これはHaskellから生まれた優れた発明であるはずです。

ただし、タイプクラスのWikipediaページ

プログラマーは、クラスに属するすべての型に存在しなければならない一連の関数名または定数名をそれぞれの型とともに指定することにより、型クラスを定義します。

私にとってはJavaのインターフェースにかなり近いようです(Wikipediaのインターフェース(Java)ページを引用):

Javaプログラミング言語のインターフェースは、クラスが実装しなければならないインターフェース(一般的な意味で)を指定するために使用される抽象型です。

これらの2つはかなり似ています。型クラスは型の動作を制限し、インターフェイスはクラスの動作を制限します。

Haskellの型クラスとJavaのインターフェースの違いと類似点は何でしょうか、それとも根本的に異なるのでしょうか。

編集:私はhaskell.orgでさえそれらが類似していることを認めていることに気づきました それらが非常に類似している(またはそれらがそうである)場合、なぜ型クラスがそのような誇大広告で扱われるのですか?

より多くの編集:うわー、とても多くの素晴らしい答え!コミュニティにどちらが最適かを判断させる必要があると思います。しかし、答えを読んでいる間、それらすべては、「型クラスができることはたくさんありますが、インターフェースがジェネリックに対応できないか、ジェネリックに対処する必要があるとだけ言っているようです。私は疑問に思わずにはいられません、型クラスができないのにインターフェースができることはありますか?また、Wikipediaがtypeclassは元々1989年の論文「アドホックなポリモーフィズムをアドホックから少なくする方法」であると主張していることに気づきました。一方、Haskellはまだ揺りかごのままで、Javaプロジェクトは1991年に開始され、1995年に最初にリリースされました。 。では、タイプクラスがインターフェースに似ているのではなく、その逆に、インターフェースがタイプクラスの影響を受けたのではないでしょうか。これを支持または反証する文書/論文はありますか?すべての回答をありがとう、それらはすべて非常に啓発的です!

すべての入力をありがとう!


3
いいえ、インターフェイスが型クラスでできないことは実際には何もできません。インターフェイスは通常、Haskellにはない組み込み機能を持つ言語で表示されるという大きな注意点があります。Javaに型クラスが追加されていれば、それらの機能も使用できます。
CAマッキャン2011

8
複数の質問がある場合は、すべてを1つの質問に詰め込むのではなく、複数の質問をする必要があります。とにかく、あなたの最後の質問に答える:Javaの主な影響はObjective-C(そして、誤って報告されることが多いC ++ではない)であり、その主な影響はSmalltalkとCです。JavaのインターフェースはObjective-Cのプロトコルの適応であり、これもまたaです。OO でのプロトコルの概念の形式化。これは、ネットワーキングでのプロトコルの概念、特にARPANetに基づいています。これはすべて、あなたが引用した論文よりずっと前に起こりました。...
イェルクWミッターク

1
... JavaへのHaskellの影響はずっと後のことであり、結局のところ、Haskellの設計者の1人であるPhil Wadlerによって共同設計されたGenericsに限定されています。
イェルクWミッターク

5
これは、Javaの最初の設計者の1人であるPatrick NaughtonによるUsenetの記事です。JavaはC ++ではなくObjective-Cに強く影響されました。残念ながら、それは非常に古く、元の投稿はGoogleのアーカイブにも表示されません。
イェルクWミッターク

8
そここの1の正確な複製として閉鎖された別の質問ですが、それは、深さの答えではより多くのを持っていますstackoverflow.com/questions/8122109/...
ベン

回答:


49

インターフェースはSomeInterface t、すべての値に型があるt -> whatever(をwhatever含まないt)型クラスのようなものだと思います。これは、Javaや類似の言語のような継承関係の場合、呼び出されるメソッドは、呼び出されるオブジェクトのタイプに依存し、それ以外には依存しないためです。

つまりadd :: t -> t -> t、インターフェイスでメソッドの引数の型と戻り値の型が型と同じであることを指定する方法がないため、複数のパラメーターで多態的であるようなインターフェイスを作成することは非常に困難です。呼び出されるオブジェクト(つまり、「自己」タイプ)。ジェネリック医薬品を使用すると、オブジェクト自体と同じタイプであることが期待される一般的なパラメータとのインタフェースを行うことによって、偽のこれまでの方法がちょっとある、などの方法をComparable<T>あなたが使用することを期待されている場合、それをしないFoo implements Comparable<Foo>ようにcompareTo(T otherobject)のようなものが型を持っていますt -> t -> Ordering。しかし、それでもプログラマーはこのルールに従う必要があり、このインターフェイスを使用する関数を作成するときに頭痛の種になるため、再帰的なジェネリック型パラメーターが必要です。

また、empty :: tここでは関数を呼び出さないため、メソッドではないため、このようなことはありません。


1
Scalaトレイト(基本的にはインターフェース)はthis.typeを許可するため、「自己型」のパラメーターを返すか受け入れることができます。Scalaには「自己型」と呼ばれる完全に別の機能があり、これとは何の関係もありません。これは概念上の違いではなく、実装上の違いです。
2014

44

インターフェースと型クラスの類似点は、関連する一連の操作に名前を付けて説明することです。操作自体は、名前、入力、出力を介して記述されます。同様に、これらの操作には多くの実装があり、実装が異なる可能性があります。

それが邪魔にならないうちに、ここにいくつかの顕著な違いがあります:

  • インターフェイスメソッドは常にオブジェクトインスタンスに関連付けられます。つまり、メソッドが呼び出されるオブジェクトである暗黙の「this」パラメーターが常に存在します。型クラス関数へのすべての入力は明示的です。
  • インターフェイスの実装は、インターフェイスを実装するクラスの一部として定義する必要があります。逆に、型クラス「インスタンス」は、関連する型から完全に分離して定義できます...別のモジュールでも。

一般に、型クラスはインターフェイスよりも強力で柔軟であると言って差し支えないと思います。文字列を実装する型の値またはインスタンスに変換するためのインターフェイスをどのように定義しますか?それは確かに不可能ではありませんが、結果は直感的でもエレガントでもありません。コンパイルされたライブラリに型のインターフェースを実装することが可能であることを望んだことがありますか?これらはどちらも、型クラスで簡単に実行できます。


1
型クラスをどのように拡張しますか?型クラスは、インターフェースがインターフェースを拡張する方法と同じように、他の型クラスを拡張できますか?
CMCDragonkai 2014

10
インターフェースでのJava 8のデフォルト実装を考慮して、この回答を更新する価値があるかもしれません。
モニカを2016

1
@CMCDragonkaiはい、たとえば、「クラス(Foo a)=> Bar a where ...」と言って、Bar型クラスがFoo型クラスを拡張することを指定できます。Javaと同様に、Haskellには複数の継承があります。
モニカ

この場合、型クラスはClojureのプロトコルと同じではありませんが、型の安全性がありますか?
nawfal 2017年

24

型クラスは、「アドホックなポリモーフィズム」を表現する構造化された方法として作成されました。これは、基本的にオーバーロードされた関数の専門用語です。タイプクラスの定義は次のようになります。

class Foobar a where
    foo :: a -> a -> Bool
    bar :: String -> a

つまり、fooクラスFoobarに属する型のいくつかの引数に関数を適用すると、fooその型に固有の実装が検索され、使用されます。これは、C ++ / C#などの言語での演算子のオーバーロードの状況と非常に似ていますが、より柔軟で一般化されています。

インターフェイスはオブジェクト指向言語でも同様の目的を果たしますが、基本的な概念は多少異なります。オブジェクト指向言語には、Haskellにはないタイプ階層の概念が組み込まれています。これは、インターフェイスがサブタイプによるオーバーロード(適切なインスタンスでのメソッドの呼び出し、スーパータイプが実装するサブタイプのインターフェイスの実装)の両方を伴う可能性があるため、問題を複雑にします。また、フラットタイプベースのディスパッチ(インターフェイスを実装する2つのクラスには、それを実装する共通のスーパークラスがない場合があるため)。サブタイピングによってさらに複雑になることを考えると、型クラスを非OO言語のオーバーロードされた関数の改良版と考える方が役立つと思います。

型クラスが定義されているのに対し、インタフェースは、一般的に、それを実装する単一のクラスに適用さ-にも注目に値する型クラスは、ディスパッチの大幅より柔軟な手段を有することであるタイプクラスの機能の署名のどこにでも現れることができます。OOインターフェースでこれに相当することは、インターフェースがそのクラスのオブジェクトを他のクラスに渡す方法を定義し、静的メソッドとコンストラクターを定義して、呼び出しコンテキストで必要な戻り値のタイプに基づいて実装を選択し、メソッドを定義することです。インターフェイスを実装するクラスと同じタイプの引数、および実際にはまったく変換されないその他のさまざまな引数を取ります。

簡単に言うと、これらは同じような目的を果たしますが、動作方法が多少異なり、型クラスは表現力が大幅に向上し、継承階層の一部ではなく固定型で動作するため、場合によっては使いやすくなります。


Haskellの型階層の理解に苦労していましたが、Omegaのような型システムの型についてどう思いますか?タイプ階層をシミュレートできますか?
CMCDragonkai 2014

@CMCDragonkai:申し訳ありませんが、オメガについてはあまり詳しくありません。
CAマッキャン2014

16

上記の回答を読みました。もう少し明確に答えられると思います。

Haskellの「型クラス」とJava / C#の「インターフェース」またはScalaの「特性」は基本的に類似しています。それらの間には概念的な違いはありませんが、実装には違いがあります。

  • Haskell型クラスは、データ型定義とは別の「インスタンス」で実装されます。C#/ Java / Scalaでは、インターフェース/トレイトをクラス定義に実装する必要があります。
  • Haskell型クラスを使用すると、this型またはself型を返すことができます。Scalaトレイトも同様です(this.type)。Scalaの「自己型」はまったく無関係の機能であることに注意してください。Java / C#では、この動作に近づくためにジェネリックを使用した厄介な回避策が必要です。
  • Haskell型クラスを使用すると、入力「this」型パラメーターなしで関数(定数を含む)を定義できます。Java / C#インターフェイスとScalaトレイトには、すべての関数で「this」入力パラメーターが必要です。
  • Haskell型クラスを使用すると、関数のデフォルト実装を定義できます。ScalaトレイトとJava 8+インターフェースも同様です。C#は、拡張メソッドを使用してこのようなものを近似できます。

2
この回答ポイントにいくつかの文献を追加するために、今日(オン(Haskell)型クラスおよび(C#)インターフェイス)を読みます。これもJavaではなくC#インターフェイスと比較されますが、言語の境界を越えたインターフェース。
daniel.kahlenberg 2016年

デフォルトの実装などのいくつかの近似は、おそらく抽象クラスを使用したC#でより効率的に行われると思いますか?
Arwin

12

ではプログラミングのマスター心、Haskellでは、Javaのインタフェースと型クラス間の類似性を説明フィルWadler、型クラスの発明者、とハスケルについてのインタビューがあります:

次のようなJavaメソッド:

   public static <T extends Comparable<T>> T min (T x, T y) 
   {
      if (x.compare(y) < 0)
            return x; 
      else
            return y; 
   }

Haskellメソッドと非常に似ています。

   min :: Ord a => a -> a -> a
   min x y  = if x < y then x else y

したがって、型クラスはインターフェイスに関連していますが、実際の対応は、上記のような型でパラメーター化された静的メソッドになります。


Phil Wadlerのホームページによると、彼はHaskellの主要な設計者であり、同時にJavaのGenerics拡張機能も設計しました。
Wong Jia Hau


8

ソフトウェア拡張とタイプクラスとの統合を読んでください。ここでは、タイプクラスがインターフェイスでは解決できない多くの問題を解決する方法の例が示されています。

ペーパーに記載されている例は次のとおりです。

  • 表現の問題、
  • フレームワーク統合問題、
  • 独立した拡張性の問題
  • 支配的な分解、散乱、もつれの専制政治。

3
リンクは上死んでいます。代わりにこれを試してください。
Justin Leitgeb 2013

1
さて、それも死んでしまった。これを
RichardW 2017

7

「誇大広告」レベルの話はできません。しかし、はい、型クラスは多くの点で似ています。私が考えることができる1つの違いは、Haskellが型クラスの操作の一部に動作を提供できることです。

class  Eq a  where
  (==), (/=) :: a -> a -> Bool
  x /= y     = not (x == y)
  x == y     = not (x /= y)

これは、型クラスのインスタンスであるものに対して、(==)equalとnot-equalの2つの操作があることを示してい(/=)ますEq。しかし、等しくない演算は、等しいという観点で定義されます(そのため、提供する必要があるのは1つだけです)。その逆も同様です。

したがって、おそらく合法ではないJavaでは、次のようになります。

interface Equal<T> {
    bool isEqual(T other) {
        return !isNotEqual(other); 
    }

    bool isNotEqual(T other) {
        return !isEqual(other); 
    }
}

そしてそれが機能する方法は、インターフェースを実装するためにそれらのメソッドの1つを提供するだけでよいということです。したがって、インターフェイスレベルで必要な動作の一種の部分的な実装を提供する機能は違います。


6

それらは似ており(読み取り:使い方が似ています)、おそらく同じように実装されます。Haskellのポリモーフィック関数は、型クラスに関連付けられた関数をリストする 'vtable'を内部で使用します。

多くの場合、このテーブルはコンパイル時に推定できます。これはおそらくJavaではあまり当てはまりません。

ただし、これはメソッドではなく関数の表です。メソッドはオブジェクトにバインドされていますが、Haskellの型クラスはバインドされていません。

それらをJavaのジェネリックのように見てください。


3

ダニエルが言うように、インターフェースの実装はデータ宣言とは別に定義されています。また、他の人が指摘したように、同じフリータイプを複数の場所で使用するオペレーションを定義する簡単な方法があります。したがって、定義するのは簡単ですNum、型クラスとしてです。したがって、Haskellでは、実際に魔法のオーバーロードされた演算子(標準の型クラスのみ)を持たなくても、演算子のオーバーロードの構文上の利点が得られます。

もう1つの違いは、その型の具体的な値がまだぶら下がっていなくても、型に基づくメソッドを使用できることです。

たとえば、read :: Read a => String -> a。したがって、「読み取り」の結果をどのように使用するかについて他のタイプ情報がぶらぶらしている場合は、コンパイラーに使用する辞書を判断させることができます。

またinstance (Read a) => Read [a] where...任意の読み取りインスタンスを定義できるようにすることもできます読み込み可能なもののリストを。Javaではそれは不可能だと思います。

そして、これらはすべて、標準の単一パラメーター型クラスであり、細工は行われていません。マルチパラメーターの型クラスを導入すると、可能性のまったく新しい世界が開かれ、さらに、関数依存性と型ファミリーによって、型システムにはるかに多くの情報と計算を埋め込むことができます。


1
マルチパラメータ型クラスはOOPでの複数ディスパッチであるので、通常の型クラスはインターフェース用です。プログラミング言語とプログラマーの頭痛の両方の力が対応して増加します。
CAマッキャン2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.