LINUX.ORG.RU

Взаимоотношения методов классов и пакетов в Lisp

 ,


0

3

Допустим, есть следующий код:

(defpackage :foo
  (:use :common-lisp)
  (:export x))
(defpackage :bar
  (:use :common-lisp :foo))

(in-package :foo)

(defgeneric m (arg))

(defun x (obj) (m obj))

(in-package :bar)

(defclass my-class () ())

(defmethod m ((obj my-class)) (print "Ok"))

(defvar obj (make-instance 'my-class))

(m obj)
(x obj)

То есть у нас есть пакет foo с функцией x, которая выполняет некоторые действия с переданным объектом, вызывая его метод m. При этом я бы хотел использовать принцип утиной типизации - если у любого объекта есть метод m, значит он умеет делать то, что нужно функции x.

Проблема возникает, если класс переданного объекта описан в другом пакете, который не импортирован в foo (в примере выше класс находится в пакете bar). SBCL ругается:

Unhandled SIMPLE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                    {10029365E3}>:
  There is no applicable method for the generic function
    #<STANDARD-GENERIC-FUNCTION FOO::M (0)>
  when called with arguments
    (#<MY-CLASS {1002A00803}>).

Как эту проблему следует решать?

★★★★★

Последнее исправление: KivApple (всего исправлений: 2)

Ответ на: комментарий от antares0

Хм... Работает. Как-то не подумал о таком варианте, спасибо большое.

Импортировать не хочу, потому что foo это типа библиотеки, которая не должна зависеть от конкретной реализации m. А классы, с которыми работает x, могут быть объявлены где-угодно в коде и отслеживать список таких модулей, а затем импортировать их все в foo как-то странно.

KivApple ★★★★★
() автор топика

У тебя проблема в том что defmethod не найдя внутри bar generic для bar::m, создал его автоматически. И у тебя сейчас там bar::m c этим методом и foo::m без методов. Потыкай describe-ом и поймешь. И обычно два in-package в одном файле не пишут. Да бы избежать неопределеностей таких.

antares0 ★★★★
()
Ответ на: комментарий от KivApple

Импортировать не хочу, потому что ...

Я бы сказал, что ты думаешь как-то неправильно применительно к лиспу. Если дергаешь generic то и импортируешь его. Это общепонятный шаблон поведения.

antares0 ★★★★
()
Ответ на: комментарий от KivApple

А классы ... могут быть объявлены где-угодно в коде и отслеживать список таких модулей, а затем импортировать их все в foo как-то странно.

А зачем тебе их импортировать в foo?

antares0 ★★★★
()

с функцией x, которая выполняет некоторые действия с переданным объектом, вызывая его метод m.

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

antares0 ★★★★
()
Последнее исправление: antares0 (всего исправлений: 1)
Ответ на: комментарий от antares0

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

KivApple ★★★★★
() автор топика

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

antares0 ★★★★
()
Ответ на: комментарий от antares0

Я тут понял в чём у меня проблема. Дело в том, что в реальном коде generic я создавал и экспортировал внутри макроса. Сначала присваивал внутри defmacro с помощью let локальной переменной значение (intern тут некоторая строка), а затем возвращал из макроса `(progn ...), в котором сначала вызывал (defgeneric ,локальная-переменная), а затем (export ',локальная-переменная). И почему-то в списке символов пакета эти методы оказываются с префиксом имя-пакета:, тогда как символы, которые я экспортировал по-обычному - нет. Внутри самого пакета с defgeneric defmethod работает нормально без двоеточий.

KivApple ★★★★★
() автор топика
Последнее исправление: KivApple (всего исправлений: 2)

Просто запомни, что все определения, классы, методы, переменные и т.д. называются (в смысле named by, а не called, чёртов русский язык) символами. И экспорт-импорт пакетный тоже на уровне символов. Чтобы методы принадлежали одному генерику они должны называться одним и тем же символом. У этого есть и «побочные эффекты». Если под одним именем объявлены, например, класс, переменная, и функция, то нельзя экспортировать только что-то одно, потому что оно всё называется одним символом.

Gentooshnik ★★★★★
()
Ответ на: комментарий от KivApple

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

Gentooshnik ★★★★★
()
Ответ на: комментарий от antares0

Нашёл и исправил ошибку совсем в другом месте, теперь всё правильно - все generic экспортируются одним модулем и импортируются другим.

Большое спасибо за пояснения насчёт того, что generic это один объект, в которых defmethod лишь добавляют новые функции.

KivApple ★★★★★
() автор топика
Ответ на: комментарий от Gentooshnik

Кстати, дополнительный вопрос:

Компиляторы C++ и некоторых других языков пытаются делать «девиртуализацию» - если им очевидно какая функция должна быть вызвана, то они заменяют косвенный вызов прямым. С перегрузкой функций там дела обстоят проще, ибо она всегда compile-time, поэтому лучше сравнить обобщённые функции Lisp с виртуальными методами (не по семантике использования, но по накладным расходам и способам оптимизации).

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

KivApple ★★★★★
() автор топика
Ответ на: комментарий от antares0

И обычно два in-package в одном файле не пишут.

Я пишу и больше. Вкусовщина же.

Oxdeadbeef ★★★
()
Ответ на: комментарий от Gentooshnik

называются (в смысле named by, а не called, чёртов русский язык) символами

«именуются», хотя тоже не слишком удачно

Virtuos86 ★★★★★
()
Ответ на: комментарий от KivApple

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

Gentooshnik ★★★★★
()
Ответ на: комментарий от Gentooshnik

Просто запомни, что все определения, классы, методы, переменные и т.д. называются (в смысле named by, а не called, чёртов русский язык) символами.

Ты им просто не владеешь. Выше правильно сказали, именуются подходит. Можно было построить фразу по другому, и там тысячи вариантов. Русский — гораздо более гибкий, выразительный и простой язык, чем английский. Одни эти жесткие атавизмы с is, чего стоят, ты бы был доволен,если бы вместо «меня зовут Вася», приходилось бы плести «мое имя есть Вася»?. Плюс, ты не можешь построить предложение произвольно, тебе надо порядок слов соблюдать, «this is ...», но «is this ...?» а в сложных случаях там столько лапши вывалится, мама не горюй.

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

portquest2016
()
Ответ на: комментарий от portquest2016

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

Суть anonimous'а словами anonimous'а.

anonymous
()
Ответ на: комментарий от portquest2016

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

Извини, чувак, не люблю врать, поэтому нет.

Gentooshnik ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.