Как мультиметоды решают проблему пространства имен?

Я изучаю дизайн языков программирования, и меня интересует вопрос о том, как заменить популярную парадигму ОО с однодиспетчерной передачей сообщений на парадигму общих функций с мультиметодами. По большей части это кажется очень простым, но недавно я застрял и был бы признателен за помощь.

Message-passing 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()

В этой проблеме "bark" - это один метод с несколькими "ветвями", которые работают по-разному в зависимости от типов аргументов. Мы реализуем "bark" один раз для каждого типа аргументов, которые нас интересуют (Dogs и Cats). Во время выполнения мы можем перебирать список животных и динамически выбирать подходящую ветвь.

(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

В этой задаче "bark" - это две концептуально разные функции, которые просто случайно имеют одинаковое имя. Тип аргумента (собака или дерево) определяет, какую функцию мы имеем в виду.


Мультиметоды элегантно решают проблему номер 1. Но я не понимаю, как они решают проблему номер 2. Например, первый из двух приведенных выше примеров может быть прямолинейно переведен в мультиметоды:

(1) Dogs and Cats using multimethods

=== in file animal.code ===

   - define generic function bark(Animal a)
   - define method bark(Dog d) : print("woof")
   - define method bark(Cat c) : print("meow")

=== in file myprogram.code ===

import animal.code
for each animal a in list-of-animals :
   bark(a)

Ключевым моментом является то, что метод bark(Dog) концептуально связан с bark(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?

В этом случае, где должна быть определена родовая функция? Должна ли она быть определена на верхнем уровне, над animal и tree? Не имеет смысла рассматривать кору для животного и дерева как два метода одной и той же родовой функции, поскольку эти две функции концептуально различны.

Насколько я знаю, я не нашел ни одной прошлой работы, которая бы решала эту проблему. Я посмотрел на мультиметоды Clojure и мультиметоды CLOS, и у них та же проблема. Я скрещиваю пальцы и надеюсь либо на элегантное решение проблемы, либо на убедительный аргумент, почему это на самом деле не проблема в реальном программировании.

Пожалуйста, дайте мне знать, если вопрос нуждается в уточнении. Это довольно тонкий (но важный) момент, как мне кажется.


Спасибо за ответы sanity, Rainer, Marcin и Matthias. Я понял ваши ответы и полностью согласен с тем, что динамическая диспетчеризация и разрешение пространства имен - это две разные вещи. CLOS не смешивает эти две идеи, в то время как традиционная ОО с передачей сообщений это делает. Это также позволяет прямолинейно расширить мультиметоды до множественного наследования.

Мой вопрос касается ситуации, когда смешение является желательным.

Ниже приведен пример того, что я имею в виду.

=== файл: 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()

Из-за смешения разрешения пространства имен с диспетчеризацией программист может наивно вызвать 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)

Поскольку get-x() из XYZ не имеет концептуального отношения к get-x() из GENE, они реализованы как отдельные общие функции. Следовательно, конечный программист (в файле my_program.code) должен явно квалифицировать get-x() и сообщить системе, какую get-x() он действительно хочет вызвать.

Это правда, что такой явный подход более понятен и легко обобщается на множественную диспетчеризацию и множественное наследование. Но использование (злоупотребление) диспетчеризации для решения проблем пространства имен является чрезвычайно удобной особенностью ОО с передачей сообщений.

Я лично чувствую, что 98% моего собственного кода адекватно выражено с помощью однодиспетчеризации и однонаследования. Я использую это удобство использования диспетчеризации для разрешения пространства имен гораздо чаще, чем множественную диспетчеризацию, поэтому я не хочу от него отказываться.

Есть ли способ получить лучшее из обоих миров? Как мне избежать необходимости явного уточнения вызовов функций при использовании мультиметодов?


Похоже, что консенсус заключается в том, что

  • мультиметоды решают проблему диспетчеризации, но не решают проблему пространства имен.
  • концептуально разные функции должны иметь разные имена, и пользователи должны ожидать, что они будут квалифицировать их вручную.

Затем я считаю, что в случаях, когда достаточно единого наследования и единого диспетчирования, ОО с передачей сообщений более удобна, чем общие функции.

Тогда это звучит как открытое исследование. Если язык предоставит механизм для мультиметодов, который также может быть использован для разрешения пространства имен, будет ли это желаемой особенностью?

Мне нравится концепция общих функций, но в настоящее время я чувствую, что они оптимизированы для того, чтобы сделать "очень трудные вещи не такими трудными" за счет того, что "тривиальные вещи слегка раздражают". Поскольку большинство кода тривиально, я все еще считаю, что эту проблему стоит решить.

32
задан Patrick Li 4 March 2012 в 17:29
поделиться