web-dev-qa-db-ja.com

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

Haskellを学習しているときに、そのtype classに気付きました。これはHaskellから生まれた素晴らしい発明と思われます。

ただし、 タイプクラスのウィキペディアページ

プログラマは、クラスに属するすべての型に存在する必要がある関数名または定数名のセットをそれぞれの型とともに指定することにより、型クラスを定義します。

これは、Javaのインターフェイスにかなり近いようです(引用 Wikipediaのインターフェイス(Java)ページ ):

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

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

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

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

MORE EDIT:うわー、たくさんの素晴らしい答え!コミュニティにどちらが最適かを判断させる必要があると思います。しかし、答えを読んでいる間、彼らはすべて「インターフェイスがジェネリックに対処できない、または対処しなければならない間にタイプクラスができることがたくさんある」と言っているようです。タイプクラスはできないのにinterfacesでできることはありますか?また、Wikipediaは1989年の論文でタイプクラスが最初に発明されたと主張していることに気づきました* "How to Haskellがまだ揺りかごにいる間、アドホックポリモーフィズムを非アドホックにします。一方、Javaプロジェクトは1991年に開始され、1995年に最初にリリースされました。したがって、タイプクラスがインターフェイスに似ている代わりに、インターフェイスがタイプクラスの影響を受けているのかもしれませんか?これをサポートまたは反証するドキュメント/ペーパーはありますか?すべての回答に感謝します。

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

94
zw324

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

邪魔にならないように、ここにいくつかの注目すべき違いがあります。

  • インターフェイスメソッドは、常にオブジェクトインスタンスに関連付けられます。言い換えると、メソッドが呼び出されるオブジェクトである暗黙の「this」パラメーターが常に存在します。型クラス関数へのすべての入力は明示的です。
  • インターフェイス実装は、インターフェイスを実装するクラスの一部として定義する必要があります。逆に、型クラス「インスタンス」は、関連する型とは完全に分離して定義できます...別のモジュールであってもです。
  • 型クラスを使用すると、定義された操作のいずれかに対して「デフォルト」実装を定義できます。インターフェースは厳密に型指定のみであり、実装されていません。

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

38
Daniel Pratt

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

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

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

インターフェイスは、OO言語で同様の目的を果たしますが、基礎となる概念は多少異なります; OO言語には、Haskellが単純にインターフェースにはサブタイプ(つまり、適切なインスタンスでメソッドを呼び出す、スーパータイプが行うインターフェースを実装するサブタイプ)によるオーバーロードと、フラットタイプベースのディスパッチ(インターフェースを実装する2つのクラスサブタイピングによって導入される巨大な追加の複雑さを考えると、型クラスを非OO言語のオーバーロード関数の改良版と考える方がより役立つことをお勧めします。

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

要するに、これらは似たような目的を果たしますが、動作方法は多少異なり、型クラスは非常に表現力があり、場合によっては、継承階層の断片ではなく固定型で動作するため、使用が簡単です。

21
C. A. McCann

上記の回答を読みました。私はもう少し明確に答えることができると感じています:

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

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

Phillip Wadlerの講演をご覧ください 信仰、進化、プログラミング言語 。ワドラーはHaskellに取り組み、Java Generics。

9
mcandre

Master of mind of Programming には、Haskellについて、JavaとHaskellの型クラスの類似点を説明する型クラスの発明者であるPhil 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

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

9
ewernli

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

論文に記載されている例は次のとおりです。

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

Haskellの多態性関数は、タイプクラスに関連付けられた関数をリストする「vtable」を内部で取ります。

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

しかし、これはfunctionsではなく、methodsの表です。メソッドはオブジェクトにバインドされますが、Haskellタイプクラスはバインドされません。

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

5
Alexandre C.

「誇大広告」レベルについて話せないのは、それがうまくいくように思える場合です。しかし、はい型クラスは多くの点で似ています。私が考えることができる1つの違いは、Haskellがいくつかの型クラスのoperationsの振る舞いを提供できることです。

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

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

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

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

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

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

5
Chris

ダニエルが言うように、インターフェースの実装はデータ宣言からseperatelyで定義されます。そして、他の人が指摘したように、複数の場所で同じ自由型を使用する操作を定義する簡単な方法があります。したがって、Numをタイプクラスとして簡単に定義できます。したがって、Haskellでは、実際に魔法のオーバーロードされた演算子を持たずに、演算子のオーバーロードの構文上の利点が得られます-標準タイプクラスだけです。

もう1つの違いは、型に具体的な値がまだない場合でも、型に基づいたメソッドを使用できることです。

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

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

そして、これらはすべて、標準の単一パラメーター型クラスであり、策略は行われていません。マルチパラメータタイプクラスを導入すると、機能の依存関係とタイプファミリがさらに可能になり、さらに多くの情報と計算をタイプシステムに埋め込むことができるようになります。

3
sclv