騒音のない世界 BLOG

コンピュータの話と、音楽の話

ポリモーフィズムをもっと理解する

ポリモーフィズムオブジェクト指向の3本柱として「継承」「カプセル化」と共に語られることが多いですが、サブクラス化してオーバーライドして…というのはポリモーフィズムの一面にすぎません。この記事ではポリモーフィズムとは何なのかを説明すると共に3種類のポリモーフィズムを取り上げ、「ポリモーフィズム」という言葉を様々な角度から見ていきたいと思います。対象読者としては全くの初学者というよりも何となくポリモーフィズムは知ってて使ったりもするけどちゃんと説明できないとか、そういう人を考えています。

ポリモーフィズムを一言で説明する

ポリモーフィズムとは、一言で説明するとどういう概念なのでしょうか。日本語訳としては、オブジェクト指向の文脈だと「多態性」、関数型の文脈だと「多相性」が多い印象ですが、どちらもピンとくる言葉ではありません。そこでまずは言葉を分解して読み解いてみたいと思います。

ポリモーフィズム(Polymorphism)という言葉はポリ(Poly)とモーフィズム(Morphism)にわけられます。ポリは「複数の」、モーフィズムは「形状」と置き換えられそうです。つまりポリモーフィズムとは「複数の形状」と読み取れます。

まだ抽象的で掴みどころがないですね。実際ポリモーフィズムという言葉自体は結構抽象的で、後ほどご紹介しますが生物学などでも用いられる幅広い言葉のようです。そこで、もう少しプログラミング的な意味に注目していこうかと思います。Wikipediaの冒頭の説明がとてもわかりやすいです。

polymorphism is the provision of a single interface to entities of different types. Polymorphism (computer science) - Wikipedia

短くすると「Single interface for different types」とでも言うのでしょうか。つまりプログラミングにおけるポリモーフィズムとは、複数の異なる型に対して共通のインタフェースを提供することです。

3種類のポリモーフィズム

ポリモーフィズムには大きく分けて3種類あります。それぞれ見ていきます。ポリモーフィックな部分だけサンプルを軽く書いておきますので雰囲気だけ感じて頂ければと思います。

Ad hoc polymorphism

いくつかの限定された型に対して共通のインタフェースを提供するポリモーフィズムです。実装上は関数のオーバーロードによって実現されます。同じ名前の関数に対して、引数や戻り値の型を変更したものを用意します。

関数オーバーロードポリモーフィズムであるというのは少し意外に感じられる方もおられるのではないでしょうか。しかしこれも確かに「複数の異なる型に対して共通のインタフェースを提供」しています。

// DogとCatに対してtouchという共通インタフェースを提供する
class Breeder {
    func touch(_ dog: Dog) {
        dog.say()
    }
    func touch(_ cat: Cat) {
        cat.say()
    }
}

let breeder = Breeder()
breeder.touch(Dog()) // bowwow
breeder.touch(Cat()) // meow

Parametric polymorphism

様々な型に適用できるようなインタフェースを提供するポリモーフィズムです。実装上はいわゆるGenericsやTemplateと呼ばれる言語機能により実現されます。型を引数として渡すことで処理を様々な型に適用することを可能にします。

関数型言語において単にポリモーフィズムと言う場合、こちらを指します。

// Animalに適合するあらゆる型に対してtouchという共通インタフェースを提供する
func touch<A: Animal>(_ animal: A) {
    animal.say()
}

touch(Dog()) // bowwow
touch(Cat()) // meow

Subtyping

実行時にサブクラスのポインタをベースクラスのポインタとして扱うことにより、ベースクラスの共通なインタフェースから各サブクラスの異なる実装を呼べるようにするポリモーフィズムです。

オブジェクト指向言語において単にポリモーフィズムという場合、こちらを指します。

// AnimalとAnimalのサブクラスにtouchという共通インタフェースを提供する
func touch(_ animal: Animal) {
    animal.say()
}

touch(Dog()) // bowwow
touch(Cat()) // meow

なぜポリモーフィズムか?

なぜそのような言語機能があると嬉しいのか?という話ですが、これは僕の個人的な考えを書かせていただきます。

ポリモーフィズムは「複数の異なる型に対して共通のインタフェースを提供すること」であるというのは前述しましたが、その本質は「共通のインタフェースを用意する」という部分であると考えています。

「共通のインタフェースを用意する」という考え方は身の回りの道具のデザインにもあふれています。PCを見ればUSBやイヤホンジャックなど、共通化されたインタフェースばかりです。これがもたらすメリットは、インタフェースに適合している機器であれば何でも取り付けられるということです。言い換えれば、共通のインタフェースは柔軟な拡張性をもたらすものであり、それが良いインタフェースデザインであれば「疎結合」も促進します。

また、共通のインタフェースにはヒューマンインタフェース上のメリットもあります。ボタンやスイッチなどの使い方が共通であれば使い方を新たに覚える必要もありませんし、ミスの割合が減ることも期待できます。

プログラミング言語も人間が使う道具なので、同様のメリットがあると考えられます。つまり柔軟な拡張性と、ミスの低減です。新しい機能を追加するときに現状のコードにほとんど変更を加えることなく自然な形で追加できたり(柔軟な拡張性)、ifやswitchで分岐することなく共通インタフェースを呼び出すようにすることで、新たな機能を追加したときに書き加える箇所を減らすことができたり、連結や分解、探索、ソートなど汎用的な処理に対して呼び出すべき関数がすぐにわかったり(ミスの低減)します。

まとめると、なぜポリモーフィズムが嬉しいのか?に対する答えは「良いインタフェースを設計するのに便利だから」です。これが僕が考えるポリモーフィズムのメリットです。

その他の分野でのポリモーフィズム

ポリモーフィズムという言葉をより深く理解するため、他分野においてどのように使われているかもさらっと見てみましたのでご参考までにどうぞ。疎い分野なので間違いなどあったらすいません。

生物学

同じ種の生物の中で形や構造などが異なることをポリモーフィズムと言います。人間であれば男女の性差、目の色や肌の色、血液型などを指し、そのうち遺伝子に基づくものを遺伝的多型(Genetic polymorphism)といいます。

物質科学

同じ化学組成だが複数の異なる結晶形をとることをポリモーフィズムと言います。温度や圧力など、結晶が生成するときの条件によって結晶系が決まるようです。例えば、グラファイトとダイヤモンドはどちらもC(炭素)からなります。

おわりに

いかがでしたでしょうか。ポリモーフィズムについて様々な角度から見ていきつつ、その概念やメリットについて考察してみました。何か新たな発見があれば幸いです。

参考

結局ほとんど英語のWikipediaを参考にしました。