Есть 2 пакета:
- packet-1 в котором определен
(defmacro generate (&body body)
`(defun some-function (args)
,@body))
детальное содержание сейчас не совсем важно - packet-2 в котором "generate" используется
(generate )
Вопрос в следующем: почему для успешного использования сегенррованной функции "some-function" внутри пакета "packet-2" требуется экспортировать из "packet-1" не только "generate", но и "some-function", а также экспортировать "some-function" из "packet-2". Если этого не делать, то при попытке использования "some-function" будет STYLE-WARNING: undefined function: SOME-FUNCTION.
Если же "generate" перенести в "packet-1", то ничего этого не требуется.
SBCL 1.0.43 x64
Не очень понятно, что и как вызывается. Неплохо бы привести пример полной последовательности действий.
Если я правильно понял, то вызов generate делается в packet-1. Ну тогда все просто - вызов generate раскрывается в определение функции some-function, которая определяется также в пакете packet-1, а это отдельный символ и его также надо экспортировать. Замените вызов generate на соответствующее определение some-function в коде, и все станет понятно.
Вызов generate в packet-2. Вот пример:
(defpackage :package-1
(:use :cl)
(:export #:generate
))(defpackage :package-2
(:use :cl
:package-1))(in-package :package-1)(defmacro generate (&body body)
`(defun some-function (arg)
,@body))(in-package :package-2)(generate
(format t "Some function called~%"));;(generate
;; (format t "Some function called ~S~%" arg))
(defun second-function ()
(some-function 1))Если раскомментировать третью строку - то все нормально. Более того, закомментированный вызов generate вместо первого тоже не проходит - неопределенная переменная arg.
Понятно, что что-то с символами и их видимостью, но что именно?
Если появляется какая-то непонятка с макросами, полезно посмортеть результат их раскрытия с помощью macroexpand. В данном случае будет такая шняга:
PACKAGE-2>
(macroexpand-1 '(generate
(format t "Some function called~%")))
(DEFUN PACKAGE-1::SOME-FUNCTION (PACKAGE-1::ARG)
(FORMAT T "Some function called~%"))Вызов generate раскрывается с использованием символа some-function из пакета packet-1, т.к. он был введен именно в том пакете в момент определения макроса generate. Чтобы иметь возможность работать с generate из любого пакета, вероятно придется воспользоваться intern:
(in-package :package-1)(defmacro generate (&body body)
`(defun ,(intern (string 'some-function)) (,(intern (string 'arg)))
,@body))(in-package :package-2)(macroexpand-1 '(generate
(format t "Some function called~%")))
(DEFUN SOME-FUNCTION (ARG)
(FORMAT T "Some function called~%"))Можно упростить задачу, например так:
(defmacro generate (&body body)
(flet ((sym (string-design) (intern (string string-design)))
`(defun ,(sym 'some-function) (,(sym 'arg))
,@body)))Тогда в случае большого количества символов будет меньше писанины.
ЗЫ. не исключаю, что изобрел велосипед и есть более адекватные способы решения проблемы.
Спасибо. Надо заметить, что это продвинуло мое понимание как это реализовано на следующий уровень :) Вот только с именами аргументов пока нет понимания, как они так реализованы, что они - пакетно-зависимые, так сазать, символы.
> как они так реализованы, что они - пакетно-зависимые
Символы создаются ридером при чтении кода и помещаются в текущий пакет. Применительно к макросам очень важно понимать, что макросы работают не с текстом, а именно с s-выражениями и уже имеющимися символами.
Вот оно - последний шаг, до которого не дошел. Фактически, все есть символ - переменная, имя функции, аргумент. Просто он получает соответствующее значение в соответствующем слоте в нужный момент. То есть, все аргументы всех функций в пакете есть отдельные символы внутри пакета, просто они не экспортируются.
Спасибо за разъяснения, просто этот вопрос как-то странно освещен везде, как неполно, что-ли.