多重ディスパッチ

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

多重ディスパッチ: Multiple dispatch)またはマルチメソッド: Multimethods)は、一部のオブジェクト指向プログラミング言語が持つ機能であり、関数メソッドがその引数の一部のデータ型によって特殊化されるものをいう。

概要[編集]

コンピュータソフトウェアを開発する際、そのソースコードサブルーチンプロシージャ、サブプログラム、関数、メソッドなどと呼ばれる名前付きのブロックで構成される。そのようなブロックを何と呼ぶかは言語によるが、それらの性格の違いは微妙である。関数の名前は、他のソースコードからそれを呼び出すのに使われる。コードが実行されたとき、そのような関数呼び出しによって制御フローは呼び出された関数へと転送され、その関数実行後に通常呼び出した箇所の直後の命令に制御が戻ってくる。

関数名は、その関数の目的や用途を表すように付けられるのが普通である。時には、いくつかの関数に同じ名前をつけるのが適切な場合もある。それは、例えば実行するタスクが概念的には同じで、単に引数のデータ型が異なるだけという場合である。このような場合、呼び出し側で名前を指定しただけでは、どのブロックを実行したらよいのか特定できない。そこで、呼び出した際の引数の個数やデータ型といった情報を使って、どの関数を呼び出すかを決めるのである。これを多重ディスパッチと呼ぶ。

一般的なオブジェクト指向言語での単一ディスパッチでは、メソッド呼び出し(Smalltalkなら「メッセージ送信」、C++なら「メンバ関数呼び出し」)を行ったとき、その引数の1つが特別に扱われ、呼び出すべきメソッドの特定に使われる。多くの言語では、この特別な引数は構文上も特別な指定をされる。例えば、その特別な引数を最初に記して、その後にドットを挟んで呼び出すべきメソッドの名前を記述する(例えば special.meth(other,args,here))。

多重ディスパッチを採用する言語では、全ての引数がメソッド選択という観点では平等に扱われる。第一引数、第二引数、第三引数とマッチングを行うが、どれか特定の引数がその関数やメソッドを「所有」しているわけではない。

初期の多重ディスパッチを採用した例として Common Lisp Object System がある。

データ型[編集]

コンパイル時にデータ型を判別する言語では、メソッドの選択もコンパイル時に行われる。このようなコンパイル時の関数選択を関数オーバーロードと呼ぶことが多い。

データ型の判別を実行時まで遅らせる言語では、関数呼び出しの引数群のデータ型を動的に判別することで関数の選択は実行時に行われる。このようにして関数選択が行われることを一般に「マルチメソッド」と呼ぶ。

動的な関数呼び出しのディスパッチを行う場合、実行時に何らかのコストが発生する。言語によってはオーバーロードとマルチメソッドの違いは曖昧であり、コンパイル時にデータ型が確定する場合はコンパイル時に選択を行い、それが不可能な場合だけ実行時に若干低速なディスパッチを実施する。

[編集]

多重ディスパッチと単一ディスパッチの違いは例を見れば明らかになるだろう。宇宙船や小惑星といったオブジェクトが出てくるゲームを想定する。2つのオブジェクトが衝突する場合、何と何が衝突するかによってプログラムは様々な反応をすると想定する。

Java[編集]

Java のように単一ディスパッチしかしない言語では、コードは次のようになる(ただし、Visitor パターンをこれに活用することも可能)。

 /* Java の "instanceof" オペレータを使って、実行時のデータ型比較をする */
 class Asteroid extends Thing {
     public void collide_with(Thing other) {
         if (other instanceof Asteroid) {
             // 小惑星と小惑星の衝突を処理
         }
         else if (other instanceof Spaceship) {
             // 小惑星と宇宙船の衝突を処理
         }
     }
 }
 
 class Spaceship extends Thing {
     public void collide_with(Thing other) {
         if (other instanceof Asteroid) {
             // 宇宙船と小惑星の衝突を処理
         }
         else if (other instanceof Spaceship) {
             // 宇宙船と宇宙船の衝突を処理
         }
     }
 }

Common Lisp[編集]

Common Lispのように多重ディスパッチをする言語では、コードは次のようになる。

 (defmethod collide-with ((x asteroid) (y asteroid))
   ;; 小惑星が小惑星に衝突する場合を処理
   ...)
 (defmethod collide-with ((x asteroid) (y spaceship))
   ;; 小惑星が宇宙船に衝突する場合を処理
   ...)
 (defmethod collide-with ((x spaceship) (y asteroid))
   ;; 宇宙船が小惑星に衝突する場合を処理
   ...)
 (defmethod collide-with ((x spaceship) (y spaceship))
   ;; 宇宙船が宇宙船に衝突する場合を処理
   ...)

このように、引数のデータ型を調べるコードを、引数部分に完全に組み込むことができている。

多重ディスパッチがあると、クラスがあって、そこにメソッドが属しているという考え方はあまり意味を持たない。collide-with という名前のメソッドは、引数ごとにそれぞれ2つのクラスと関連付けられている「普通の関数呼び出し」に過ぎなくなる。結果として、メソッドを呼び出す際の特殊な構文を必要としない。

C++[編集]

C++のオーバーロードはコンパイル時に静的に引数のデータ型を調べることでなされる。従って、実行時に動的に引数のデータ型を調べる多重ディスパッチとは異なる。

Python[編集]

言語として多重ディスパッチをサポートしていない場合でも、ライブラリによる拡張で多重ディスパッチ機能を追加することは可能である。例えば、PythonCLOS風のマルチメソッド機能を追加するモジュール multimethods.py がある。

 from multimethods import Dispatch
 from game_objects import Asteroid, Spaceship
 from game_behaviors import ASFunc, SSFunc, SAFunc
 collide = Dispatch()
 collide.add_rule((Asteroid, Spaceship), ASFunc)
 collide.add_rule((Spaceship, Spaceship), SSFunc)
 collide.add_rule((Spaceship, Asteroid), SAFunc)
 def AAFunc(a, b):
     "Behavior when asteroid hits asteroid"
     # ...define new behavior...
 collide.add_rule((Asteroid, Asteroid), AAFunc)
 
 # ...later...
 collide(thing1, thing2)

機能的には CLOS の例によく似ているが、その構文は通常の Python のものである。

Python 2.4 の decorators を使って、グイド・ヴァンロッサムはマルチメソッドのサンプル実装を行い[1]、構文を単純化した。

 @multimethod(Asteroid, Asteroid)
 def collide(a, b):
     "Behavior when asteroid hits asteroid"
     # ...define new behavior...
 @multimethod(Asteroid, Spaceship)
 def collide(a, b):
     "Behavior when asteroid hits spaceship"
     # ...define new behavior...
 # ... define other multimethod rules ...

プログラミング言語におけるサポート[編集]

汎用のマルチメソッド機能をサポートするプログラミング言語は次のとおりである。

何らかの拡張でマルチメソッドをサポートする言語として、次のものがある。

参考文献[編集]