私はプログラミング言語の設計を研究しており、一般的な単一ディスパッチ メッセージ パッシング OO パラダイムをマルチメソッド ジェネリック関数パラダイムに置き換える方法の問題に興味があります。ほとんどの場合、それは非常に簡単に思えますが、最近行き詰まってしまったので、助けていただければ幸いです。
私の考えでは、メッセージ パッシング OO は、2 つの異なる問題を解決する 1 つのソリューションです。次の疑似コードで、私が何を意味するかを詳しく説明します。
(1) ディスパッチの問題を解決します。
=== ファイル animal.code ===
- Animals can "bark"
- Dogs "bark" by printing "woof" to the screen.
- Cats "bark" by printing "meow" to the screen.
=== ファイル内 myprogram.code ===
import animal.code
for each animal a in list-of-animals :
a.bark()
この問題では、「樹皮」は、引数の型に応じて異なる動作をする複数の「分岐」を持つ 1 つのメソッドです。対象の引数の型 (Dogs と Cats) ごとに「bark」を 1 回実装します。実行時に、動物のリストを繰り返し処理し、適切な分岐を動的に選択できます。
(2) 名前空間の問題を解決します。
=== ファイル animal.code ===
- Animals can "bark"
=== ファイル tree.code ===
- Trees have "bark"
=== ファイル内 myprogram.code ===
import animal.code
import tree.code
a = new-dog()
a.bark() //Make the dog bark
…
t = new-tree()
b = t.bark() //Retrieve the bark from the tree
この問題では、「樹皮」は、実際には、たまたま同じ名前を持つ 2 つの概念的に異なる機能です。引数の型 (dog か tree か) によって、実際にどちらの関数を意味するかが決まります。
マルチメソッドは問題番号 1 をエレガントに解決します。しかし、問題番号 2 をどのように解決するかはわかりません。たとえば、上記の 2 つの例の最初の例は、単純な方法でマルチメソッドに変換できます。
(1) マルチメソッドを使用する犬と猫
=== ファイル animal.code ===
- define generic function bark(Animal a)
- define method bark(Dog d) : print("woof")
- define method bark(Cat c) : print("meow")
=== ファイル内 myprogram.code ===
import animal.code
for each animal a in list-of-animals :
bark(a)
キーポイントは、メソッド bark(Dog) が概念的に bark(Cat) に関連していることです。2 番目の例にはこの属性がありません。そのため、マルチメソッドが名前空間の問題を解決する方法がわかりません。
(2) マルチメソッドが動物と木で機能しない理由
=== ファイル animal.code ===
- define generic function bark(Animal a)
=== ファイル tree.code ===
- define generic function bark(Tree t)
=== ファイル内 myprogram.code ===
import animal.code
import tree.code
a = new-dog()
bark(a) /// Which bark function are we calling?
t = new-tree
bark(t) /// Which bark function are we calling?
この場合、ジェネリック関数はどこに定義する必要がありますか? animal と tree の両方の上にあるトップレベルで定義する必要がありますか? 2 つの関数は概念的に異なるため、動物と木の樹皮を同じジェネリック関数の 2 つのメソッドと考えるのは意味がありません。
私の知る限り、この問題を解決した過去の作品はまだ見つかっていません。Clojure マルチメソッドと CLOS マルチメソッドを見てきましたが、同じ問題があります。私は指を交差させて、問題に対するエレガントな解決策、または実際のプログラミングで実際に問題にならない理由についての説得力のある議論のいずれかを望んでいます。
質問に明確化が必要な場合はお知らせください。これはかなり微妙な(しかし重要な)ポイントだと思います。
返事の正気、Rainer、Marcin、および Matthias に感謝します。私はあなたの返信を理解し、動的ディスパッチと名前空間の解決が 2 つの異なるものであることに完全に同意します。CLOS は 2 つのアイデアを混同しませんが、従来のメッセージ パッシング OO は混同します。これにより、マルチメソッドを多重継承に簡単に拡張することもできます。
私の質問は、具体的には、融合が望ましい状況にあります。
以下は、私が言いたいことの例です。
=== ファイル: XYZ.code ===
define class XYZ :
define get-x ()
define get-y ()
define get-z ()
=== ファイル: POINT.code ===
define class POINT :
define get-x ()
define get-y ()
=== ファイル: GENE.code ===
define class GENE :
define get-x ()
define get-xx ()
define get-y ()
define get-xy ()
==== ファイル: my_program.code ===
import XYZ.code
import POINT.code
import GENE.code
obj = new-xyz()
obj.get-x()
pt = new-point()
pt.get-x()
gene = new-point()
gene.get-x()
名前空間の解決とディスパッチが混同されているため、プログラマーは 3 つのオブジェクトすべてに対して単純に get-x() を呼び出すことができます。これも完全に明白です。各オブジェクトは独自のメソッド セットを「所有」しているため、プログラマーが何を意図したかについて混乱することはありません。
これをマルチメソッド バージョンと比較してください。
=== ファイル: XYZ.code ===
define generic function get-x (XYZ)
define generic function get-y (XYZ)
define generic function get-z (XYZ)
=== ファイル: POINT.code ===
define generic function get-x (POINT)
define generic function get-y (POINT)
=== ファイル: GENE.code ===
define generic function get-x (GENE)
define generic function get-xx (GENE)
define generic function get-y (GENE)
define generic function get-xy (GENE)
==== ファイル: my_program.code ===
import XYZ.code
import POINT.code
import GENE.code
obj = new-xyz()
XYZ:get-x(obj)
pt = new-point()
POINT:get-x(pt)
gene = new-point()
GENE:get-x(gene)
XYZ の get-x() は GENE の get-x() と概念的な関係がないため、これらは別のジェネリック関数として実装されています。したがって、エンド プログラマー (my_program.code 内) は get-x() を明示的に修飾し、実際にどのget-x() を呼び出すつもりかをシステムに伝える必要があります。
確かに、この明示的なアプローチはより明確であり、複数のディスパッチと複数の継承に簡単に一般化できます。しかし、名前空間の問題を解決するためにディスパッチを (悪用して) 使用することは、メッセージ パッシング OO の非常に便利な機能です。
個人的には、自分のコードの 98% が単一ディスパッチと単一継承を使用して適切に表現されていると感じています。私は複数のディスパッチを使用するよりも、名前空間の解決にディスパッチを使用するこの便利さを使用しているため、それをあきらめたくありません。
両方の長所を活かす方法はありますか? マルチメソッド設定で関数呼び出しを明示的に修飾する必要をなくすにはどうすればよいですか?
という意見が一致しているようです
- マルチメソッドはディスパッチの問題を解決しますが、名前空間の問題には取り組みません。
- 概念的に異なる関数には異なる名前を付ける必要があり、ユーザーはそれらを手動で修飾する必要があります。
したがって、単一継承の単一ディスパッチで十分な場合は、ジェネリック関数よりもメッセージ パッシング OO の方が便利だと思います。
これはオープンリサーチのようですね。名前空間の解決にも使用できるマルチメソッドのメカニズムを言語が提供する場合、それは望ましい機能でしょうか?
私はジェネリック関数の概念が好きですが、現在のところ、「些細なことを少し煩わしく」することを犠牲にして、「非常に難しいことをそれほど難しくない」ように最適化されていると感じています. コードの大部分は些細なものなので、これは解決する価値のある問題であると私は信じています。