← Архив: Common Lisp

Генерация функций макросами

Author: · 01.03.2011 15:13
· original author: amartynov
Есть 2 пакета:
  • packet-1 в котором определен
    (defmacro generate (&body body)
      `(defun some-function (args)
          ,@body
    )
    )

    детальное содержание сейчас не совсем важно
  • packet-2 в котором "generate" используется
    (generate ;some code
    )

Вопрос в следующем: почему для успешного использования сегенррованной функции "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
· original author: allchemist
Не очень понятно, что и как вызывается. Неплохо бы привести пример полной последовательности действий.
· original author: allchemist
Если я правильно понял, то вызов generate делается в packet-1. Ну тогда все просто - вызов generate раскрывается в определение функции some-function, которая определяется также в пакете packet-1, а это отдельный символ и его также надо экспортировать. Замените вызов generate на соответствующее определение some-function в коде, и все станет понятно.
· original author: amartynov
Вызов generate в packet-2. Вот пример:
(defpackage :package-1
  (:use :cl)
  (:export #:generate
           ;;; #:some-function
  
)
)

(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.
Понятно, что что-то с символами и их видимостью, но что именно?
· original author: allchemist
Если появляется какая-то непонятка с макросами, полезно посмортеть результат их раскрытия с помощью 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
)
)
)

Тогда в случае большого количества символов будет меньше писанины.
ЗЫ. не исключаю, что изобрел велосипед и есть более адекватные способы решения проблемы.
· original author: amartynov
Спасибо. Надо заметить, что это продвинуло мое понимание как это реализовано на следующий уровень :) Вот только с именами аргументов пока нет понимания, как они так реализованы, что они - пакетно-зависимые, так сазать, символы.
· original author: archimag
> как они так реализованы, что они - пакетно-зависимые
Символы создаются ридером при чтении кода и помещаются в текущий пакет. Применительно к макросам очень важно понимать, что макросы работают не с текстом, а именно с s-выражениями и уже имеющимися символами.
· original author: amartynov
Вот оно - последний шаг, до которого не дошел. Фактически, все есть символ - переменная, имя функции, аргумент. Просто он получает соответствующее значение в соответствующем слоте в нужный момент. То есть, все аргументы всех функций в пакете есть отдельные символы внутри пакета, просто они не экспортируются.
Спасибо за разъяснения, просто этот вопрос как-то странно освещен везде, как неполно, что-ли.