ポリモーフィズム

出典: フリー百科事典『ウィキペディア(Wikipedia)』
移動先: 案内検索

ポリモーフィズム: Polymorphism)とは、プログラミング言語型システムの性質を表すもので、プログラミング言語の各要素(定数変数オブジェクト関数メソッドなど)についてそれらが複数の型に属することを許すという性質を指す。ポリモルフィズム多態性多相性多様性とも呼ばれる。対義語はモノモーフィズム(Monomorphism)、単態性単相性で、プログラミング言語の各要素が唯一つの型に属するという性質を指す。

ポリモーフィズムは次のようないくつかの種類に分けられる。

  • アドホック多相(Ad hoc polymorphism):ある関数が、異なる型の引数に対してそれぞれ異なる実装を持つ場合。多くのプログラミング言語で関数の多重定義としてサポートされる。
  • パラメータ多相(Parametric polymorphism):コードが特定の型を指定せずに書かれることで、さまざまな型に対して透過的に使用できる場合。オブジェクト指向言語ではジェネリクスやジェネリックプログラミングとしても知られる。関数型言語の分野ではパラメータ多相のことを指して単に多相性と呼ぶ場合がある。
  • 部分型付け(Subtyping):部分型多相(subtype polymorphism)や包含多相(inclusion polymorphism)とも。共通の上位型をもつ複数の型を、1つの名前で扱う場合。オブジェクト指向言語の分野では部分型付けのことを指して単にポリモーフィズムと呼ぶ場合がある。

パラメータ多相と部分型付けを組み合わせることで、変性有界量化英語版の考え方に発展する。

[編集]

モノモーフィズムな型システムを持つプログラミング言語では関数や手続きはそれぞれ一意に識別される名前と結びついており、従って異なった動作を実現するためには異なった名前を用いる必要があった。

例えば、何かの値を文字列形式に変換する最も単純な場合を考える。モノモーフィズムな型システムを持つ言語では、次のように別々の関数になっていなければならない。

古典的な変換関数:
数値を文字列にする場合
string = StringFromNumber(number)
日付値を文字列にする場合
string = StringFromDate(date)

一方ポリモーフィズムな型システムを持つ言語では、StringValue のような汎用の述語を定義し、型別にそれぞれ適切な変換方式を定義させることでオブジェクトの種別によらない抽象度の高い変換形式を実現できる。

多態を行なう変換方式:
見た目上、型によらない変換が可能
 string = number.StringValue
 string = date.StringValue

無論、StringValueの定義は型別に行なわなければならないので、総体として記述量が減少するわけではない(継承による再利用はありうる)。また、何をもって「正しい動作」とするのかはオブジェクトの設計に依存するため、多態を使いこなすにはシステム全体を見通す優れた設計能力が要求される。

歴史[編集]

アドホック多相およびパラメータ多相という分類は、クリストファー・ストレイチーが1967年に講義ノートとして著したFundamental Concepts in Programming Languages英語版で初めて導入された[1]。 このストレイチーの分類はPeter Wegner英語版Luca Cardelli英語版が1985年に発表した論文のなかで拡張され、部分型や継承を説明するために包含多相という分類が導入された[2]。 なお部分型付けや継承の実装自体は1967年に登場したSimulaから既に存在していた。

ポリモーフィズムの種類[編集]

アドホック多相[編集]

関数や演算子の多重定義のように、同じ名前で型の異なる引数に適用できて、その振る舞いは引数の型によって違うような関数の多相性のことをアドホック多相という。「ad hoc(その場しのぎの)」という用語は悪い意味で使われているのではなく、単にこの種の多相性が型システムの基本的な機能ではないという事実を指して使われている。次のPascalでの例では、Add関数は呼び出し側からは様々な型に対して総称的に動作するかのように見えるが、コンパイラから見ればこれらは全く別個の2つの関数である。

program Adhoc;

function Add(x, y : Integer) : Integer;
begin
    Add := x + y
end;

function Add(s, t : String) : String;
begin
    Add := Concat(s, t)
end;

begin
    Writeln(Add(1, 2));                   (* Prints "3"             *)
    Writeln(Add('Hello, ', 'World!'));    (* Prints "Hello, World!" *)
end.

動的型付け言語では、実行されるべき正しい関数が実行時まで決定できない可能性があるという点で、状況はより複雑になりうる。

暗黙の型変換も型強制多相(coercion polymorphism)としてアドホック多相の一形態と定義される[2][3]

パラメータ多相[編集]

パラメータ多相を使うと、値の型に関係なく均一に値を扱うことで、関数やデータ型を総称的に記述できるようになる[4]。パラメータ多相は言語の静的な型安全性を保ちながら表現力を向上させる手法のひとつである。

パラメータ多相の概念は関数とデータ型の両方に適用される。異なる型の値に対して評価、適用可能な関数のことを多相な関数という。総称化された型とみなすことができるデータ型(例えば任意の型の要素を持てるリスト)は多相なデータ型という。

パラメータ多相性は関数型プログラミングの分野では至るところに現れるため、しばしば単に「多相性」と言われることがある。次のHaskellの例ではパラメータ化されたリストと2つのパラメータ多相な関数を示す。

data List a = Nil | Cons a (List a)

length :: List a -> Integer
length Nil         = 0
length (Cons x xs) = 1 + length xs

map :: (a -> b) -> List a -> List b
map f Nil         = Nil
map f (Cons x xs) = Cons (f x) (map f xs)

パラメータ多相は様々なオブジェクト指向言語でも利用できる。例えばC++やD言語のテンプレート、JavaやC#のジェネリクスなどである。

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) {
    ...
}

ジャン=イヴ・ジラールJohn C. Reynolds英語版は、それぞれ独立に、パラメータ多相の概念をラムダ計算の拡張(System Fとか多相ラムダ計算と呼ばれる)として形式的に発展させた。

部分型付け[編集]

いくつかのプログラミング言語では、特定の多相性の状況において使用できる型の範囲を制限するために部分型付け部分型多相とも呼ばれる)を採用している。そういった言語で部分型付けを使用すると、ある型Tのオブジェクトを受け取る関数は、Tの部分型である型Sのオブジェクトを渡された場合でも正しく動作する(リスコフの置換原則)。この型の関係性はしばしばS <: Tと表記される。一般的に部分型多相は動的に解決される(後述)。

次の例ではAnimalの部分型としてCatDogを用意する。プロシージャletsHear()Animal型の引数を受け取るが、その部分型の引数を渡しても問題なく動作する。

abstract class Animal {
    abstract String talk();
}

class Cat extends Animal {
    String talk() {
        return "Meow!";
    }
}

class Dog extends Animal {
    String talk() {
        return "Woof!";
    }
}

void letsHear(final Animal a) {
    println(a.talk());
}

int main() {
    letsHear(new Cat());
    letsHear(new Dog());
}

オブジェクト指向言語はサブクラス化(継承)によって部分型多相を提供する。典型的な実装では、各クラスはそれぞれ仮想関数テーブル(vtable)と呼ばれる関数のテーブルを持ち、各オブジェクトは自らのクラスのvtableへのポインタを持つ。多相なメソッドを呼出すときには、このvtableを参照する。

多くのオブジェクト指向言語では、仮想関数の呼出しに1番目の引数(thisオブジェクト)の vtable だけを参照する単一ディスパッチを採用している。つまりその他の引数の実行時の型は仮想関数の呼び出しに全く無関係である。一方でCommon Lisp Object Systemなどでは、メソッドの呼出しが全ての引数に対して多相的となる多重ディスパッチを採用している。

実装的側面[編集]

静的ポリモーフィズムと動的ポリモーフィズム[編集]

ポリモーフィズムは実装がいつ選択されるかによって、静的(コンパイル時)か動的(実行時、典型的には仮想関数などを通して)かに区別できる。これはそれぞれ静的ディスパッチと動的ディスパッチとして知られ、さらにこれらに対応するポリモーフィズムはそれぞれ静的ポリモーフィズムと動的ポリモーフィズムと呼ばれる。

動的ディスパッチのオーバーヘッドが無いため、静的ポリモーフィズムはより高速に実行できるが、追加的なコンパイラの補助を必要とする。静的ポリモーフィズムではコンパイラやソースコード解析ツール、プログラマの目視による、より広範な静的解析(特に最適化)が可能となる。動的ポリモーフィズムはより柔軟だが速度はより遅くなる。例えば動的ディスパッチではダック・タイピングが可能で、動的にリンクされたライブラリはオブジェクトの型を知らなくても動作できるだろう。

典型的にはアドホック多相とパラメータ多相は静的ポリモーフィズムとして動作し、部分型多相は動的ポリモーフィズムとして動作する。しかし、奇妙に再帰したテンプレートパターンのような洗練されたテンプレートメタプログラミングを通して、部分型多相で静的ポリモーフィズムを実現することも可能である。

関連項目[編集]

出典[編集]

  1. ^ C. Strachey – Fundamental Concepts in Programming Languages http://www.itu.dk/courses/BPRD/E2009/fundamental-1967.pdf
  2. ^ a b Cardelli, Luca; Wegner, Peter (December 1985). “On understanding types, data abstraction, and polymorphism”. ACM Computing Surveys (New York, NY, USA: ACM) 17 (4): 471–523. doi:10.1145/6041.6042. ISSN 0360-0300. http://lucacardelli.name/Papers/OnUnderstanding.A4.pdf. 
  3. ^ Allen B. Tucker (28 June 2004). Computer Science Handbook, Second Edition. Taylor & Francis. pp. 91–. ISBN 978-1-58488-360-9. https://books.google.com/books?id=9IFMCsQJyscC&pg=SA91-PA5. 
  4. ^ Pierce, B. C. 2002 Types and Programming Languages. MIT Press.