ポリモーフィズム

ポリモーフィズム: Polymorphism)とは、コンピュータプログラミング型システムにおいて、それぞれ異なる型の実体に一つのインターフェースを提供するための形式手法であり、またはそれぞれ異なる型の多重定義を一つのシンボルで表現するための形式手法である。ここでの実体とはデータ構造関数機能の双方を指している。この邦訳には多態性、多相性、多様性などが当てられている。このコンセプトは、有機組織ないし生物の種は様々な形態と段階を持つという生物学からの借用語である。

ポリモーフィズムは、通常以下の三種に分けられる。

  • アドホック多相(Ad hoc polymorphism)- 主に論理的凝集による実体の集合に一つの共通インターフェースを持たせる。これはただの共通シンボルの付加によって行われる。その代表例の関数オーバーロードは、値域+写像名という共通シンボルで異なる定義域をまとめている。
  • パラメトリック多相(Parametric polymorphism)- 部分的に共通な型群を一つのシンボルで扱う。その型が内包する1個以上の要素型の詳細を明示しないで部分的に抽象化されたままの型を表現する。その要素型の詳細は、インスタンス化された実体ごとに確定される。ジェネリックプログラミングの土台である。
  • サブタイピング(Subtyping)- サブタイプ多相(Subtype polymorphism)やインクルージョン多相(Inclusion polymorphism)とも。共通の上位型から派生した様々な下位型の実体を一つの共通シンボルで操作する。オブジェクト指向の多態性とは専らこれを指す。

サブタイピングとパラメトリック多相の融合は、共変性/反変性および型境界/有界量化英語版の手法に発展する。四種目に挙げられることがあるロー多相英語版型理論のレコード型の可変長要素による多態性であり、ダックタイピングの土台になっている。対義語はモノモーフィズム(Monomorphism)単態性であり、これは実体を一つの型のみに属させるコンセプトである。

歴史編集

ポリモーフィックな型システムの研究は1960年代から始められている。1967年に計算機科学者クリストファー・ストレイチーが著した講義ノートFundamental Concepts in Programming Languages英語版のなかで、アドホック多相とパラメトリック多相という概念が初めて提唱されている[1]。アドホック多相は「ALGOL68」で実践され、パラメトリック多相は「ML」の型システムで実践された。 1985年に計算機科学者ピーター・ウェグナー英語版ルカ・カーデリ英語版は、「Simula67」の継承と動的ディスパッチによるサブタイピングの機能を説明するためのインクルージョン多相という概念を提唱した[2]。これとストレイチー提唱の二つを合わせて三本柱にした型システム概念が、ポリモーフィズムと呼ばれるようになっている。

1989年にオブジェクト指向動的型付けを説明するためのロー多相英語版という概念が情報工学者ミッチェル・ワンド英語版の著作で提唱されているが、知名度は低い。同時期にパラメトリック多相とメタプログラミングを融合したジェネリックプログラミングが確立されており、アドホック多相とジェネリックプログラミングを融合した型クラスが「Haskell」で登場している。2003年の「Scala」はサブタイピングとパラメトリック多相を融合した共変性と反変性の機能を導入した。パラメトリック多相は関数型言語の型システムで拡張され続けており、高階カインド・存在型・型ファミリ・カインド多相・依存型などが登場している。

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

アドホック多相編集

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

#include <iostream>
#include <string>

int Add(int x, int y) {
    return x + y;
}

std::string Add(const std::string& s1, const std::string& s2) {
    return s1 + s2;
}

int main() {
    std::cout << Add(1, 2) << std::endl; // "3" が出力される。
    std::cout << Add("Hello, ", "World!") << std::endl; // "Hello, World!" が出力される。
}

動的型付け言語では、実行されるべき正しい関数が実行時まで決定できない可能性があるという点で、状況はより複雑になりうる。暗黙の型変換も型強制多相(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 GetLength() { ... }
}

List<B> Map(Func<A, B> f, List<A> xs) {
    ...
}

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

サブタイピング編集

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

次のJavaの例では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!";
    }
}

class Test {
    static void letsHear(final Animal a) {
        System.out.println(a.talk());
    }
    public static void main(String[] args) {
        letsHear(new Cat());
        letsHear(new Dog());
    }
}

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

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

ポリモーフィズムの実装的側面編集

静的な多態性と動的な多態性編集

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

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

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

単態性と多態性編集

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

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

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

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

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

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

関連項目編集

出典編集

  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.