プログラミング言語理論と型理論において、ポリモーフィズムは値または変数が複数の型を持つことを可能にし、特定の操作を複数の型の値に対して実行することを可能にする。[ 1 ]
オブジェクト指向プログラミングにおいて、ポリモーフィズムとは、異なるデータ型のエンティティに対して単一のインターフェースを提供するものである。[ 2 ]この概念は、生物や種が多くの異なる形態や段階を持つことができるという生物学 の原理から借用されたものである。 [ 3 ]
多型性の最も一般的に認識されている主要な形態は次のとおりです。
多相型システムへの関心は1990年代に大きく高まり、10年の終わりまでに実用的な実装が現れ始めました。アドホック多相とパラメトリック多相は、もともとChristopher Stracheyの『プログラミング言語の基本概念』 [ 5 ]で説明されており、そこでは多相の「2つの主要なクラス」として挙げられています。アドホック多相はALGOL 68の機能であり、パラメトリック多相はMLの型システムのコア機能でした。
1985年の論文で、ピーター・ウェグナーとルカ・カルデッリは、サブタイプと継承をモデル化するために包含多相性という用語を導入し、[ 1 ] Simulaをそれを実装した最初のプログラミング言語として挙げた。
Christopher Strachey は、異なる型の引数に適用できるが、適用される引数の型によって動作が異なる多相関数を指す用語として、アドホック多相という用語を選択しました(関数オーバーロードまたは演算子オーバーロードとも呼ばれます)。[ 5 ]ここで「アドホック」とは、この形式の多相が型システムの基本的な機能ではないことを意味します。以下のJava の例では、呼び出しを見るとadd関数は 2 つの型 (整数と文字列) に対して汎用的に動作しているように見えますが、コンパイラによって完全に異なる関数として扱われます。
class AdHocPolymorphic { public String add ( int x , int y ) { return String . format ( "合計: %d" , x + y ); }public String add ( String name ) { return String.format ( " Added " , name ); } }public class Adhoc { public static void main ( String [] args ) { AdHocPolymorphic poly = new AdHocPolymorphic ();System.out.println ( poly.add ( 1 , 2 )); // "合計: 3" と出力System.out.println ( poly.add ( " Jay " ) ) ; // " Jayを追加しました"と出力} }動的型付け言語では、呼び出すべき正しい関数が実行時にしか判断できない場合があるため、状況はより複雑になる可能性がある。
パラメトリック多相性により、関数やデータ型を汎用的に記述できるため、型に依存しずに値を均一に処理できます。 [ 7 ]パラメトリック多相性は、完全な静的型安全性を維持しながら、言語の表現力を高める方法です。
パラメトリック多相性の概念は、データ型と関数の両方に適用されます。異なる型の値に評価または適用できる関数は、多相関数と呼ばれます。汎用型のように見えるデータ型(例えば、任意の型の要素を持つリスト)は、そのような特殊化が行われる汎用型と同様に、多相データ型と呼ばれます。
パラメトリック多相性は関数型プログラミングにおいて広く用いられており、しばしば単に「多相性」と呼ばれます。次のHaskellの例では、パラメータ化されたリストデータ型と、それに対する2つのパラメトリック多相性関数を示します。
データリストa = Nil | Cons a (リストa )length :: List a -> Integer length Nil = 0 length ( Cons x xs ) = 1 + length xsmap :: ( a -> b ) -> List a -> List b map f Nil = Nil map f ( Cons x xs ) = Cons ( f x ) ( map f xs )パラメトリック多相性は、いくつかのオブジェクト指向言語でも利用可能です。例えば、C++やD言語のテンプレート、あるいはC#、Delphi、Java、Go言語のジェネリクスなどが挙げられます。
class List < T > { class Node < T > { T elem ; Node < T > next ; } Node < T > head ; int length () { ... } }List <B> map ( Func < A , B > f , List <A> xs ) { ... }ジョン・C・レイノルズ(そして後にジャン=イヴ・ジラール)は、ラムダ計算の拡張としてこの多相性の概念を正式に発展させました(多相ラムダ計算またはシステムFと呼ばれます)。パラメトリックに多相な関数は、その値ではなくデータの形状に基づいて動作するため、できることが必然的に制限され、パラメトリック性の概念につながります。
一部のプログラミング言語では、サブタイピング(サブタイプ多相性または包含多相性とも呼ばれる)の概念を用いて、特定の多相性の場合に使用できる型の範囲を制限します。これらの言語では、サブタイピングによって、特定の型Tのオブジェクトを受け取る関数を記述できるだけでなく、 Tのサブタイプである型Sに属するオブジェクトが渡された場合にも正しく動作するようにすることができます(リスコフの置換原理による)。この型関係は、S <: Tと表記されることがあります。逆に、TはSのスーパータイプであるとされ、T :> Sと表記されます。サブタイプ多相性は通常、動的に解決されます(下記参照)。
以下のJavaの例では、猫と犬はペットのサブタイプとして定義されています。このプロシージャはletsHear()ペットを受け取りますが、サブタイプが渡された場合でも正しく動作します。
abstract class Pet { abstract String speak (); }class Cat extends Pet { String speak () { return "ニャー!" ; } }class Dog extends Pet { String speak () { return "ワン!" ; } }static void letsHear ( final Pet pet ) { System.out.println ( pet.speak ( ) ) ; }static void main ( String [] args ) { letsHear ( new Cat ()); letsHear ( new Dog ()); }![]()
別の例として、Number、Rational、Integer がNumber :> RationalおよびNumber :> Integerとなる型である場合( RationalとInteger はそれらのスーパータイプである型Numberのサブタイプ)、 Number を受け取るように記述された関数は、 IntegerまたはRationalが渡された場合もNumberが渡された場合も同様に機能します。オブジェクトの実際の型は、クライアントからブラックボックスに隠蔽され、オブジェクトのアイデンティティを介してアクセスできます。Number型が抽象型の場合、最も派生した型がNumberであるオブジェクトにアクセスできない場合があります(抽象データ型、抽象クラスを参照)。この種の型階層は、特にScheme 言語のコンテキストでは数値タワーとして知られており、通常はさらに多くの型が含まれています。
オブジェクト指向プログラミング言語は、サブクラス化(継承とも呼ばれる)を用いてサブタイプ多態性を提供します。一般的な実装では、各クラスは仮想テーブル(略してvtable )と呼ばれるもの、つまりクラスインターフェースの多態性を実装する関数のテーブルを含み、各オブジェクトはそのクラスのvtableへのポインタを保持しており、多態性メソッドが呼び出されるたびにこのポインタが参照されます。このメカニズムは、次の例に該当します。
this、他の引数の実行時型は全く関係ないからです。他の多くの一般的なオブジェクトシステムも同様です。ただし、Common Lisp Object Systemのように、多重ディスパッチを提供するシステムもあり、その場合、メソッド呼び出しはすべての引数に対して多態性を持ちます。
パラメトリック多型性とサブタイピングの相互作用は、型バリアンスと境界付き量化の概念につながる。
行多相性[ 8 ]は、サブタイピングと似ているが、異なる概念である。これは構造型を扱う。特定の特性を持つすべての値の使用を可能にし、残りの型情報を失うことはない。
関連する概念として、多型性(またはデータ型の汎用性)があります。多型関数は多相関数よりも一般的であり、そのような関数では、「特定のデータ型に対して固定のアドホックなケースを提供することはできますが、アドホックなコンビネータは存在しません」。[ 9 ]
ランク多相性は、 APLのような配列プログラミング言語の決定的な特徴の 1 つです。ランク多相性プログラミング モデルの本質は、すべての操作を暗黙的に集約操作として扱い、任意の次元の配列で使用できることです[ 10 ]。つまり、ランク多相性により、任意の形状とサイズの配列を操作する関数を定義できます。
ポリモーフィズムは、実装が選択されるタイミングによって区別されます。静的(コンパイル時)に選択されるか、動的(実行時、通常は仮想関数を介して)に選択されるかです。これはそれぞれ静的ディスパッチと動的ディスパッチと呼ばれ、対応するポリモーフィズムの形式は、それぞれ静的ポリモーフィズムと動的ポリモーフィズムと呼ばれます。
静的ポリモーフィズムは、動的ディスパッチのオーバーヘッドがないため実行速度が速いですが、コンパイラの追加サポートが必要です。さらに、静的ポリモーフィズムは、コンパイラ(特に最適化のため)、ソースコード解析ツール、および人間(プログラマ)によるより高度な静的解析を可能にします。動的ポリモーフィズムは柔軟性が高いものの、処理速度は遅くなります。例えば、動的ポリモーフィズムではダックタイピングが可能であり、動的にリンクされたライブラリは、オブジェクトの完全な型を知らなくても操作を行うことができます。
静的多型性は一般的にアドホック多型性やパラメトリック多型性で発生するのに対し、動的多型性はサブタイプ多型性でよく見られる。しかし、テンプレートメタプログラミング、特に奇妙な繰り返しテンプレートパターンをより高度に利用することで、サブタイピングによる静的多型性を実現することも可能である。
ライブラリを介してポリモーフィズムが公開されると、共有オブジェクトが構築されるときにパラメータの型を知る方法がないため、動的ライブラリでは静的ポリモーフィズムは不可能になります。C++ や Rust のような言語はモノモーフィズムのテンプレートを使用しますが、Swift プログラミング言語はデフォルトで動的ディスパッチを多用してこれらのライブラリのアプリケーションバイナリインターフェイスを構築します。その結果、実行時のオーバーヘッドを犠牲にして、より多くのコードを共有してシステムサイズを削減できます。[ 11 ]
Pascal などの従来の型付き言語は、関数と手続き、したがってそれらのオペランドが一意の型を持つという考えに基づいています。このような言語は、すべての値と変数が 1 つの型のみであると解釈できるという意味で、
単相的で
あると言われます。単相プログラミング言語は、一部の値と変数が複数の型を持つことができる
多相
言語とは対照的です。
多相関数とは、オペランド(実引数)が複数の型を持つことができる関数のことです。多相型とは、その演算が複数の型の値に適用できる型のことです。
ポリモーフィズム – 異なる型のエンティティに単一のインターフェースを提供する。