Я исследую проектирование языка программирования, и меня интересует вопрос о том, как заменить популярную парадигму ОО с передачей однократных сообщений на парадигму универсальной функции мультиметода.По большей части, это кажется очень простым, но я недавно застрял и был бы признателен за некоторую помощь.
OO передачи сообщений, на мой взгляд, это одно решение, которое решает два различных проблемы.Я объясню, что я имею в виду подробно в следующем псевдокоде.
(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()
В этой задаче «кора» - это один метод с несколькими «ветвями», которые работают по-разному в зависимости от типов аргументов.Мы внедряем «лай» один раз для каждого интересующего нас типа аргумента (собаки и кошки).Во время выполнения мы можем перебирать список животных и динамически выбирать подходящую ветвь.
(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
В этой задаче «кора» - это две концептуально разные функции, которые просто оказываются с одинаковыми именами.Тип аргумента (собака или дерево) определяет, какую функцию мы на самом деле имеем в виду.
Мультиметоды элегантно решают задачу № 1. Но я не понимаю, как они решают задачу № 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)
Ключевым моментом является то, что метод коры (Dog) концептуально связан с корой (Cat).Во втором примере этот атрибут отсутствует, поэтому я не понимаю, как мультиметоды решают проблему с пространством имен.
(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?
В этом случае, где должна быть определена общая функция?Должно ли оно быть определено на верхнем уровне, выше как животного, так и дерева?Не имеет смысла думать о коре для животных и деревьев как о двух методах одной и той же родовой функции, потому что две функции концептуально различны.
Насколько я знаю, я не нашел ни одной прошлой работы, которая бы решала эту проблему.Я посмотрел на мультиметоды Clojure и мультиметоды CLOS, и у них та же проблема.Я скрещиваю пальцы и надеюсь либо на элегантное решение проблемы, либо на убедительный аргумент, почему на самом деле это не проблема в реальном программировании.
Пожалуйста, дайте мне знать, если вопрос требует уточнения.Я думаю, что это довольно тонкий (но важный) момент.
Спасибо за здравомыслие, Райнер, Марцин и Матиас.Я понимаю ваши ответы и полностью согласен с тем, что динамическая диспетчеризация и разрешение пространства имен - это две разные вещи.CLOS не объединяет две идеи, в отличие от традиционной ОО с передачей сообщений.Это также допускает прямое расширение мультиметодов на множественное наследование.
Мой вопрос, в частности, касается ситуации, когда соотношение желательно .
Ниже приведен примерчто я имею в виду=== файл: 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()
Из-за связи разрешения пространства имен с диспетчеризацией программист может наивно вызывать 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)
==== file: 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)
Поскольку get-x () XYZ не имеет концептуального отношения к get-x () GENE, они реализованы как отдельные универсальные функции. Следовательно, конечный программист (в my_program.code) должен явно квалифицировать get-x () и сообщить системе , какую get-x () он на самом деле хочет вызвать.
Это правда, что этот явный подход более понятен и легко обобщается на множественную диспетчеризацию и множественное наследование. Но использование (злоупотребление) диспетчеризацией для решения проблем с пространством имен является чрезвычайно удобной особенностью ОО передачи сообщений.
Лично я чувствую, что 98% моего собственного кода адекватно выражены с помощью однократной отправки и однократного наследования. Я гораздо удобнее использую диспетчеризацию для разрешения пространства имен, чем многократную диспетчеризацию, поэтому не хочу отказываться от нее.
Есть ли способ получить мне лучшее из обоих миров? Как избежать необходимости явной квалификации моих вызовов функций в настройке нескольких методов?
Кажется, консенсус таков, что
- мультиметоды решают проблему диспетчеризации, но не атакуют проблему пространства имен.
- функции, которые концептуально отличаются должны иметь разные имена, и пользователи должны ожидать их ручной квалификации.
Затем я полагаю, что в случаях, когда однопроцессная одиночная передача достаточна, ОО с передачей сообщений удобнее, чем универсальные функции.
Звучит так, будто это открытое исследование. Если бы язык предоставил механизм для мультиметодов, который также может использоваться для разрешения пространства имен, это было бы желательным свойством?
Мне нравится концепция универсальных функций, но в настоящее время я чувствую, что они оптимизированы для создания "очень сложных вещей, не таких сложных" за счет создания "простых вещей, которые немного раздражают". Поскольку большая часть кода тривиальна, я по-прежнему считаю, что эту проблему стоит решить.