Я изучаю дизайн языка программирования, и меня интересует вопрос о том, как заменить популярную однонаправленную передачу сообщений с помощью парадигмы OO с парадигмой generic-function мультиметодов. По большей части это кажется очень простым, но я недавно застрял и хотел бы помочь.
Передача сообщений 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)
Ключевым моментом является то, что метод коры (Собака) концептуально связан с корой (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 multimethods и CLOS multimethods, и они имеют ту же проблему. Я скрещиваю пальцы и надеюсь на элегантное решение проблемы или на убедительный аргумент в пользу того, почему это действительно не проблема в реальном программировании.
Пожалуйста, дайте мне знать, если вопрос нуждается в разъяснении. Я думаю, что это довольно тонкая (но важная) точка.
Спасибо за ответы здравомыслия, Райнер, Марцин и Маттиас. Я понимаю ваши ответы и полностью согласен с тем, что динамическая диспетчеризация и разрешение пространства имен - это две разные вещи. CLOS не объединяет две идеи, тогда как традиционная передача сообщений OO делает. Это также позволяет прямому расширению мультиметодов к множественному наследованию.
Мой вопрос конкретно заключается в ситуации, когда желательно объединение.
Ниже приведен пример того, что я имею в виду.
=== file: XYZ.code ===
define class XYZ :
define get-x ()
define get-y ()
define get-z ()
=== file: 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 ()
==== file: 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() для всех трех объектов. Это также совершенно однозначно. Каждый объект "владеет" своим собственным набором методов, поэтому нет путаницы в отношении того, что имел в виду программист.
Сравните это с версией с несколькими вариантами:
=== file: XYZ.code ===
define generic function get-x (XYZ)
define generic function get-y (XYZ)
define generic function get-z (XYZ)
=== file: 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() он на самом деле означает для вызова.
Верно, что этот явный подход более ясен и легко обобщается на множественную отправку и множественное наследование. Но использование (злоупотребление) отправки для решения проблем с пространством имен является чрезвычайно удобной функцией передачи сообщений OO.
Я лично считаю, что 98% моего собственного кода адекватно выражены с помощью однонаправленного и однонаправленного. Я использую это удобство использования отправки для разрешения пространства имен намного больше, чем использование множественной отправки, поэтому я неохотно отказываюсь от него.
Есть ли способ получить лучшее из обоих миров? Как мне избежать необходимости явно квалифицировать мои вызовы функций в настройке нескольких методов?
Похоже, что консенсус в том, что
- multimethods решает проблему отправки, но не атакует проблему с пространством имен.
- функции, концептуально различающиеся, должны иметь разные имена, и от пользователей следует ожидать их вручную.
Затем я полагаю, что в случаях, когда однонаправленная однократная нагрузка достаточна, передача сообщений OO более удобна, чем общие функции.
Похоже, это открытое исследование. Если язык должен был обеспечить механизм для многотодовых методов, который также может использоваться для разрешения пространства имен, это будет желаемая функция?
Мне нравится концепция родовых функций, но в настоящее время они чувствуют, что они оптимизированы для того, чтобы "делать очень тяжелые вещи не так сложно" за счет того, что "тривиальные вещи слегка раздражали". Поскольку большая часть кода тривиальна, я по-прежнему считаю, что это стоящая проблема для решения.