web-dev-qa-db-ja.com

パラメトリックポリモーフィズムとアドホックポリモーフィズム

Java/Scala/C++言語でのジェネリッククラス/関数のポリモーフィズムなどのパラメトリックポリモーフィズムとHaskell型システムでの「アドホック」ポリモーフィズムの主な違いを理解したいと思います。私は第1種の言語に精通していますが、Haskellを使用したことがありません。

より正確に:

  1. 型推論アルゴリズムはどうですか? Java Haskellの型推論とは異なりますか?
  2. 何かをJava/Scalaで書くことができるがHaskellで書くことができない(これらのプラットフォームのモジュラー機能にもよる)状況、およびその逆の例を教えてください。

前もって感謝します。

55
Ilya Lakhin

[〜#〜] tapl [〜#〜] に従って、§23.2:

パラメトリックポリモーフィズム(...)を使用すると、実際の型の代わりに変数を使用して1つのコードを「一般的に」型指定し、必要に応じて特定の型でインスタンス化できます。パラメトリック定義は均一です。それらのインスタンスはすべて同じように動作します。 (...)

対照的に、アドホックポリモーフィズムは、ポリモーフィック値が異なるタイプで「表示」されたときに異なる動作を示すことを可能にします。アドホックなポリモーフィズムの最も一般的な例はオーバーロードです。これは、単一の関数シンボルを多くの実装に関連付けます。コンパイラー(または、オーバーロードの解決が静的か動的かによって、ランタイムシステム)は、引数のタイプに基づいて、関数の各アプリケーションに適切な実装を選択します。

したがって、歴史の連続する段階を考慮すると、非一般的な公式Java(別名プレ( J2SE 5. 、2004年9月以降))にはアドホックなポリモーフィズムがありました。 メソッドのオーバーロード -ただし、パラメトリックなポリモーフィズムではないため、 一般的なメソッドを記述する はできません。その後、もちろん両方を行うことができます。

比較すると、その最初から 1990年 であるHaskellはパラメトリックに多態的であり、次のように記述できます。

swap :: (A; B) -> (B; A)
swap (x; y) = (y; x)

ここで、AとBは型変数です。変数は、仮定なしにall型にインスタンス化できます。

しかし、ad-hocポリモーフィズムを与える既存の構成要素はありませんでした。これは、severalに適用される関数を記述できるようにしますが、すべてではないタイプ。タイプクラスは、この目標を達成する方法として実装されました。

class(Javaインターフェースに似たもの)を記述し、型シグネチャジェネリック型に実装したい関数。次に、このクラスに一致するいくつかの(そしてうまくいけばseveralinstancesを登録できます。それまでの間、次のような一般的なメソッドを記述できます。

between :: (Ord a)  a -> a -> a -> Bool
between x y z = x ≤ y ^ y ≤ z

ここで、Ordは、関数(_ ≤ _)を定義するクラスです。使用すると、(between "abc" "d" "ghi")静的に解決されて文字列に適切なインスタンスが選択されます(整数などではなく)-正確に(Javaの)メソッドがオーバーロードされる瞬間。

Javaでも バインドされたワイルドカード を使用して同様のことができます。しかし、その前のHaskellとJavaの主な違いは、Haskellだけが自動的に辞書を渡すことができるということです:両方の言語でOrd Tの2つのインスタンスを指定すると、b0b1を使用すると、これらを引数として受け取り、ペアのタイプ(b0, b1)のインスタンスを、たとえば辞書式順序を使用して生成するf関数を作成できます。 (("hello", 2), ((3, "hi"), 5))が与えられたとしましょう。 Javaでは、stringをそのオブジェクトに適用するために、intおよびfのインスタンスを覚えて、正しいインスタンス(betweenの4つのアプリケーションで構成される)を渡す必要があります。 Haskellは compositionality を適用し、グラウンドインスタンスとfコンストラクター(これはもちろん他のコンストラクターにも拡張されます)のみを指定して正しいインスタンスを構築する方法を理解できます。


今、type inferenceに関する限り(そしてこれはおそらく別の質問になるはずです)、両方の言語でincomplete、コンパイラがタイプを判別できないn-annotatedプログラムを常に記述できるという意味で。

  1. haskellの場合、これは impredicative (a.k.a. first-class)ポリモーフィズムがあり、型推論が決定できないためです。その点で、Javaは1次多型(Scalaが展開するもの)に限定されることに注意してください。

  2. javaの場合、これは 反変サブタイプ をサポートするためです。

しかし、これらの言語は主に、実際の型推論が適用されるプログラムステートメントの範囲と、型推論の結果の正確性の重要性が異なります。

  1. Haskellの場合、推論はすべての「高度にポリモーフィックではない」用語に適用され、よく知られているアルゴリズムの公開された拡張に基づいて健全な結果を返すように真剣に取り組みます。

    • 基本的に、Haskellの推論は Hindley-Milner に基づいています。これにより、アプリケーションのタイプを推論するとすぐに完全な結果が得られますtype変数 (たとえば、上記の例のAおよびB)は、non-polymorphic型でのみインスタンス化できます(単純化していますが、これは基本的にMLスタイルのポリモーフィズムです)たとえばOcamlで)。
    • 最近のGHCは、型注釈が必要になる可能性があることを確認しています Damas-Milner型ではないletバインディングまたはλ抽象化の場合のみ
    • Haskellは、その最も毛深い拡張機能(例 GADTs )でも、この推測可能なコアに比較的近くとどまろうとしました。とにかく、提案された拡張はほとんど常に、拡張型推論の正しさの証明を伴う論文に含まれています。
  2. Javaの場合、型推論はずっと限定的な方法で適用されますとにかく:

    Java 5のリリース前は、Javaでの型推論はありませんでした。 Java言語カルチャによると、すべての変数、メソッド、および動的に割り当てられたオブジェクトのタイプは、プログラマーが明示的に宣言する必要があります。ジェネリック(クラスごとにパラメーター化されたクラスとメソッド)がJava 5に導入されたとき、言語は、変数、メソッド、および割り当てに関するこの要件を保持しました。しかし、ポリモーフィックメソッド(タイプごとにパラメーター化)の導入により、(i)プログラマーがすべてのポリモーフィックメソッド呼び出しサイトでメソッドタイプ引数を提供するか、または(ii)言語がメソッドタイプ引数の推論をサポートするかが決まりました。プログラマーに追加の事務的負担をかけないようにするために、Java 5の設計者は、型推論を実行して型引数を決定することを選択しましたポリモーフィックメソッド呼び出しの場合。 ( ソース 、強調鉱山)

    推論アルゴリズム は本質的に GJのそれ ですが、 somewhatkludgy の後にワイルドカードが追加されています(注ただし、J2SE 6.0で行われた可能な修正については最新ではありません)。アプローチの概念的な大きな違いは、Javaの推論はlocalであり、式の推論された型は型システムから生成された制約とその型にのみ依存するという意味です部分式、ただしコンテキスト上ではありません。

    不完全で、時には正しくない型の推論に関するパーティーラインは、比較的くつろいでいることに注意してください。 仕様 に従って:

    また、型推論はどのようにも健全性に影響を与えないことにも注意してください。推論された型が無意味な場合、呼び出しにより型エラーが発生します。型推論アルゴリズムは、実際によく機能するように設計された、ヒューリスティックと見なす必要があります。望ましい結果を推測できない場合は、代わりに明示的な型のパラメーターを使用できます。

67
huitseeker

Parametric polymorphismは、型を気にせず、どの型にも同じ関数を実装することを意味します。たとえば、Haskellの場合:

length :: [a] -> Int
length [] = 0          
length (x:xs) = 1 + length xs

リストの要素のタイプが何であるかは関係ありません。いくつあるかは関係ありません。

Ad-hoc polymorphism(aka method overloading)ただし、パラメーターのタイプに応じて異なる実装を使用することを意味します。

Haskellの例を示します。 makeBreakfastという関数を定義するとします。

入力パラメータがEggsの場合、makeBreakfastに卵の作り方に関するメッセージを返してほしい。

入力パラメーターがPancakesの場合、makeBreakfastにパンケーキの作成方法に関するメッセージを返してほしい。

BreakfastFood関数を実装するmakeBreakfastというタイプクラスを作成します。 makeBreakfastの実装は、makeBreakfastへの入力のタイプに応じて異なります。

class BreakfastFood food where
  makeBreakfast :: food -> String

instance BreakfastFood Eggs where
  makeBreakfast = "First crack 'em, then fry 'em"

instance BreakfastFood Toast where
  makeBreakfast = "Put bread in the toaster until brown"

John Mitchellのプログラミング言語の概念によれば、

パラメトリックポリモーフィズムとオーバーロード(別名アドホックポリモーフィズム)の主な違いは、パラメータポリモーフィック関数が1つのアルゴリズムを使用してさまざまな型の引数を操作するのに対し、オーバーロード関数は引数のタイプごとに異なるアルゴリズムを使用する可能性があることです。

24
Rose Perrone

パラメトリック多態性とその場限りの多態性の意味と、それらがHaskellとJavaで利用できる範囲)についての完全な議論は長めですが、具体的な質問にはもっと簡単に取り組むことができます。

型推論のアルゴリズムの例Java Haskellの型推論との違いは?

私の知る限り、Javaは型推論を行いません。そのため、Haskellがそれを行うという違いがあります。

何かをJava/Scalaで書くことができるがHaskellで書くことができない(これらのプラットフォームのモジュラー機能にもよる)状況、およびその逆の例を教えてください。

Javaできないのは、Haskellができることの非常に単純な例の1つです。maxBound :: Bounded a => aを定義することはできません。Java Haskellができないことを実行できます。

0
Daniel Wagner