多重ディスパッチ

多重ディスパッチ（英: Multiple dispatch）は、多重定義された関数やメソッド（マルチメソッド（英: Multimethods）などと呼ばれる）などについて、そこで呼び出されるべき1つの定義を動的に選んで実行する（動的ディスパッチする）際に、2個以上の複数の引数が関与してどれかひとつを選ぶこと（特殊化）がおこなわれるものである。 多重定義を許すプログラミング言語では、同一の名前の（すなわち、多重定義された）関数やメソッドのうちのどれを呼出す（ディスパッチする）かを決定する、ということをしなければならない。 多くのオブジェクト指向プログラミング言語は単一ディスパッチである。すなわち、メソッド呼び出し（Smalltalkなら「メッセージ送信」、C++なら「メンバ関数呼び出し」）において、引数の1つが特別に扱われ、呼び出すべきメソッドの特定に使われる。構文上もその引数を特別に扱い、ドットを挟んで、そのオブジェクトを選択する式と、呼び出すべきメソッドの名前を記述する（例えばspecial.meth(other,args,here)）。（これは、そのような言語ではそのメソッドをそのオブジェクトが「所有する」ような形となるため、オブジェクトによって名前空間を指定するのが自然であるため、という理由もある） 多重ディスパッチを採用する言語では、全ての引数をメソッド選択に参加させることが可能である。第一引数、第二引数、第三引数とマッチングを行うが、どれか特定の引数がその関数やメソッドを「所有」しているわけではない。二項演算子(関数)のような、2引数の場合のみを対象とする多重ディスパッチをダブルディスパッチといい、関数やメソッドの多重ディスパッチは無い言語でも、二項演算子にはダブルディスパッチがある、といった言語もある。 多重ディスパッチを採用した初期の例として Common Lisp (Common Lisp Object System) がある。 多重ディスパッチと単一ディスパッチの違いは例を見れば明らかになるだろう。宇宙船や小惑星といったオブジェクトが出てくるゲームを想定する。2つのオブジェクトが衝突する場合、何と何が衝突するかによってプログラムは様々な反応をすると想定する。 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のように多重ディスパッチをする言語では、コードは次のようになる。 (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つのクラスと関連付けられている「普通の関数呼び出し」に過ぎなくなる。結果として、メソッドを呼び出す際の特殊な構文を必要としない。 言語として多重ディスパッチをサポートしていない場合でも、ライブラリによる拡張で多重ディスパッチ機能を追加することは可能である。例えば、Python にCommon Lisp風のマルチメソッド機能を追加するモジュール 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) 機能的には Common Lisp の例によく似ているが、その構文は通常の 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 ... 以下は、何らかの拡張なしに、多重ディスパッチのような機構を持つ言語の例である。 Common Lisp (Common Lisp Object System) Clojure (Multimethods and Hierarchies) Dylan Nice Slate Cecil Raku Julia 以下は、多重ディスパッチのような機構のための何らかの拡張の例（及びその言語）である。 Scheme (Tiny CLOS) Python (gnosis.magic.multimethods) Perl (Class:Multimethods) Java (MultiJava) Ruby (The Multiple Dispatch Library, Multimethod Package) MSc Thesis on Multiple Dispatch 魅力的なPython: 多重ディスパッチ IBM