Подтвердить что ты не робот

Как многоточие решает проблему пространства имен?

Я изучаю дизайн языка программирования, и меня интересует вопрос о том, как заменить популярную однонаправленную передачу сообщений с помощью парадигмы 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 более удобна, чем общие функции.

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

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

4b9b3361

Ответ 1

Динамическая диспетчеризация и разрешение пространства имен - это две разные вещи. Во многих объектных системах классы также используются для пространств имен. Также обратите внимание, что часто и класс, и пространство имен привязаны к файлу. Таким образом, эти объектные системы объединяют по крайней мере три вещи:

  • определения классов с их слотами и методами
  • пространство имен для идентификаторов
  • блок хранения исходного кода

Общий Lisp и его объектная система (CLOS) работают по-разному:

  • классы не образуют пространство имен
  • общие функции и методы не относятся к классам и поэтому не определены внутри классов
  • общие функции определяются как функции верхнего уровня и, следовательно, не являются вложенными или локальными
  • Идентификаторы для общих функций - это символы
  • символы имеют свой собственный механизм пространства имен, называемый пакетами
  • общие функции являются "открытыми". Можно добавлять или удалять методы в любое время
  • Общие функции - это объекты первого класса.
  • mathods - это объекты первого класса.
  • классы и общие функции также не объединены с файлами. Вы можете определить несколько классов и несколько общих функций в одном файле или в любом количестве файлов по вашему желанию. Вы также можете определить классы и методы для запуска кода (таким образом, не привязанного к файлам) или что-то вроде REPL (читайте цикл eval print).

Стиль в CLOS:

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

Пример:

(defpackage "ANIMAL" (:use "CL")) 
(in-package "ANIMAL")

(defclass animal () ())
(deflcass dog (animal) ())
(deflcass cat (animal) ()))

(defmethod bark ((an-animal dog)) (print 'woof))
(defmethod bark ((an-animal cat)) (print 'meow)) 

(bark (make-instance 'dog))
(bark (make-instance 'dog))

Обратите внимание, что класс ANIMAL и пакет ANIMAL имеют одинаковое имя. Но это не обязательно так. Имена никак не связаны. DEFMETHOD неявно создает соответствующую общую функцию.

Если вы добавите другой пакет (например, GAME-ANIMALS), то общая функция BARK будет отличаться. Если эти пакеты не связаны (например, один пакет использует другой).

В другом пакете (пространство имен символов в Common Lisp) можно вызвать следующее:

(animal:bark some-animal)

(game-animal:bark some-game-animal)

Символ имеет синтаксис

PACKAGE-NAME::SYMBOL-NAME

Если пакет совпадает с текущим пакетом, его можно опустить.

  • ANIMAL::BARK относится к символу с именем BARK в пакете ANIMAL. Обратите внимание, что есть два двоеточия.
  • AINMAL:BARK относится к экспортированному символу BARK в пакете ANIMAL. Обратите внимание, что существует только один двоеточие. Экспорт, импорт и использование - это механизмы, определенные для пакетов и их символов. Таким образом, они не зависят от классов и общих функций, но могут быть использованы для структурирования пространства имен для обозначений этих символов.

Более интересным является случай, когда многообразные методы используются в общих функциях:

(defmethod bite ((some-animal cat) (some-human human))
  ...)

(defmethod bite ((some-animal dog) (some-food bone))
  ...)

Выше используются классы CAT, HUMAN, DOG и BONE. Какому классу должна принадлежать общая функция? Как выглядит специальное пространство имен?

Поскольку общие функции отправляют по всем аргументам, не имеет прямого смысла объединять общую функцию со специальным пространством имен и сделать ее определением в одном классе.

Мотивация:

Общие функции были добавлены в 80-х годах до Lisp разработчиками в Xerox PARC (для Common LOOPS) и в Symbolics for New Flavors. Один хотел избавиться от дополнительного механизма вызова (передачи сообщений) и доставить диспетчер к обычным (верхним уровням) функциям. У New Flavors была единственная отправка, но общие функции с несколькими аргументами. Исследование Common LOOPS затем привело к многократной отправке. Новые ароматы и общие LOOPS были заменены стандартизированным CLOS. Затем эти идеи были переведены на другие языки, такие как Дилан.

Так как примерный код в вопросе не использует ничего, что могут предложить общие функции, похоже, что нужно что-то отказаться.

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

При

(defmethod bark ((some-animal dog)) ...)
(defmethod bark ((some-tree oak)) ...)

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

Но больше:

(defmethod bark ((some-animal dog) tone loudness duration)
   ...)

(defmethod bark ((some-tree oak)) ...)

Теперь неожиданно список параметров для одной и той же названной общей функции выглядит иначе. Должна ли быть разрешена одна общая функция? Если нет, как мы называем BARK для разных объектов в списке вещей с правильными параметрами?

В реальных Lisp кодах общие функции обычно выглядят намного сложнее с несколькими необязательными и необязательными аргументами.

В Common Lisp общие функции также не только имеют один тип метода. Существуют различные типы методов и различные способы их комбинирования. Это имеет смысл только объединить их, когда они действительно принадлежат к определенной общей функции.

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

Для простого случая, когда у меня есть объект, который имеет координаты x и y и может действовать как точка, я бы наследовал класс объектов из класса POINT (возможно, как некоторый mixin). Затем я импортировал символы GET-X и GET-Y в некоторое пространство имен - там, где это необходимо.

Существуют и другие языки, которые отличаются от Lisp/CLOS и которые пытаются (ed) поддерживать мультиметоды:

Кажется, есть много попыток добавить его в Java.

Ответ 2

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

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

Ответ 3

Общие функции должны выполнять один и тот же "глагол" для всех классов, для которых реализуется его метод.

В случае "коры животных" / "дерево" животное-глагол "выполняет звуковое действие", а в древовидном ящике, я полагаю, он делает "защитный экран".

То, что английский называет их "корой", является лишь лингвистической совпадением.

Если у вас есть случай, когда несколько разных GF (общие функции) действительно должны иметь одно и то же имя, использование пространств имен для их разделения (вероятно) правильное.

Ответ 4

Передача сообщений OO, в общем, не решает проблему с именами, о которой вы говорите. Языки OO со структурными типами не различают метод bark в Animal или Tree, если они имеют один и тот же тип. Это происходит только потому, что популярные языки OO используют системы именного типа (например, Java), которые выглядят так.

Ответ 5

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

Конечно. Но поскольку их arglist один и тот же (просто передавая объект методу), вы можете "реализовать" их как разные методы в одной и той же общей функции.

Единственное ограничение при добавлении метода к общей функции состоит в том, что arglist метода соответствует arglist общей функции.

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

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

Хотя даже это ограничение (требуется тот же самый arglist), кажется, время от времени ограничивается. Если вы посмотрите на Erlang, функции имеют arity, и вы можете определить несколько функций с тем же именем, которые имеют различную arity (функции с тем же именем и разными аргументами). И тогда какая-то отправка позаботится о правильной функции. Мне это нравится. И в lisp, я думаю, это сопоставило бы с тем, что универсальная функция принимает методы, которые имеют разные аргументы. Может быть, это что-то, что настраивается в MOP?

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

Метод может "принять" & ключ и аргументы останова, определенные в его общем посредством наличия параметра & rest, посредством того же ключа & параметров или путем указания & allow-other-keys вместе с & ключом. метод также может указывать ключевые параметры, не найденные в общем Список параметров функции - когда вызывается общая функция, любой & ключевой параметр, заданный общей функцией или любым применимым метод будет принят.

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

Так как они оба заключены в одну и ту же общую функцию, вы будете называть их точно так же:

(bark tree-obj) -> Returns a noun (the bark of the tree)
(bark dog-obj) -> Produces a verb (the dog barks)

Как код:

CL-USER> 
(defclass tree ()
  ((bark :accessor bark :initarg :bark :initform 'cracked)))
#<STANDARD-CLASS TREE>
CL-USER> 
(symbol-function 'bark)
#<STANDARD-GENERIC-FUNCTION BARK (1)>
CL-USER> 
(defclass dog ()
  ())
#<STANDARD-CLASS DOG>
CL-USER> 
(defmethod bark ((obj dog))
  'rough)
#<STANDARD-METHOD BARK (DOG) {1005494691}>
CL-USER> 
(symbol-function 'bark)
#<STANDARD-GENERIC-FUNCTION BARK (2)>
CL-USER> 
(bark (make-instance 'tree))
CRACKED
CL-USER> 
(bark (make-instance 'dog))
ROUGH
CL-USER> 

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

Ответ 6

Вы работаете с несколькими понятиями и смешиваете их, например: пространства имен, глобальные общие функции, локальные общие функции (методы), вызов метода, передачу сообщений и т.д.

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

Функциональные языки, не моя сила, я сделал некоторую работу с LISP.

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

Например, что-то, что я считаю очень важным для использования пространства имен ( "модули" ), как отдельное понятие из процедурного программирования, и избегать конфликтов идентификаторов, как те, которые вы упомянули. Язык программирования с пространством имен, подобным вашему, будет следующим:

=== в файле animal.code ===

define module animals

define class animal
  // methods doesn't use "bark(animal AANIMAL)"
  define method bark()
  ...
  end define method
end define class

define class dog
  // methods doesn't use "bark(dog ADOG)"
  define method bark()
  ...
  end define method
end define class

end define module

=== в файле myprogram.code ===

define module myprogram

import animals.code
import trees.code

define function main
  a = new-dog()
  a.bark() //Make the dog bark

  …

  t = new-tree()
  b = t.bark() //Retrieve the bark from the tree
end define function main

end define module

Приветствия.