#macros #common-lisp
#макросы #common-lisp
Вопрос:
Сначала я ожидал, что это будет просто — учитывая, что это Common Lisp с самым мощным из всех макросов всех языков. Но теперь, 8 часов спустя, у меня есть запятые и квазикавычки, выходящие из моей задней части, и я ни на шаг не ближе к решению.
Мне нужен макрос, который производит 3 функции. Его подпись выглядит следующим образом:
(defmacro remote (name arg-names amp;body body) ...)
И он должен генерироваться (я использую «foo» в качестве аргумента name в тексте ниже):
-
(defun foo (,arg-names) ,@body )
(просто прямая реализация foo), которая может быть вызвана как(foo arg1 arg2 ... )
. -
(defun remote-foo (args) ...
который может быть вызван как(remote-foo arg1 arg2 ...)
и возвращает заключенный в кавычки вызов.(foo arg1 arg2 ...)
-
(defun remote-defun-foo () ...
который при вызове подобным образом(remote-defun-foo)
возвращает заключенное в кавычки определение функции(defun foo (,args) ,@body)
.
Я нашел решение для 1. Но я по-прежнему не понимаю, как записать 2. и 3. Вот что у меня есть на данный момент.
(defun make-remote-name (name)
(read-from-string (format nil "remote-~a" name)))
(defun make-definition-name (name)
(read-from-string (format nil "remote-defun-~a" name)))
(defmacro remoted (name arg-names amp;body body)
`(progn
(defun ,name ,arg-names ,@body) ;; 1.
))
Я не уверен — но для 2. и 3. Кажется, мне нужно что-то вроде вложенных квазикавычек или других трюков, которые я никогда раньше не использовал.
Ответ №1:
Как описано, это не сложно:
(defmacro define-remote (name (amp;rest args) amp;body decls/forms)
(let ((remote-name (intern (format nil "REMOTE-~A" (symbol-name name))))
(remote-defun-name (intern (format nil "REMOTE-DEFUN-~A"
(symbol-name name))))
(def `(defun ,name (,@args) ,@decls/forms)))
`(progn
,def
(defun ,remote-name (,@args) `(,',name ,,@args))
(defun ,remote-defun-name ()
',def)
',name)))
Затем
> (macroexpand '(define-remote foo (x) x))
(progn
(defun foo (x) x)
(defun remote-foo (x) `(foo ,x))
(defun remote-defun-foo () '(defun foo (x) x))
'foo)
t
Неудобный бит (который в более ранней версии этого ответа был ошибочным, поскольку я неправильно истолковал вопрос) — это вторая форма, в которой вы хотите создать вызов в кавычках, используя аргументы из созданной вами функции, поэтому вам придется немного повозиться с вложенными обратными кавычками.
Способ, который я нахожу, помогает понять это, — переписать вторую форму в терминах list
, а затем запомнить, что
(list x y)
это то же самое, что
`(,x ,y)
(Оказывается, я понятия не имею, как вернуть кавычки во встроенный код в markdown.)
Однако у всех них есть неприятная скрытая проблема: вторая форма в расширении, как правило, будет неправильной. Рассмотрим это:
> (macroexpand '(define-remote foo (amp;key (x 1)) x))
(progn
(defun foo (amp;key (x 1)) x)
(defun remote-foo (amp;key (x 1)) `(foo ,amp;key ,(x 1)))
(defun remote-defun-foo () '(defun foo (amp;key (x 1)) x))
'foo)
t
remote-foo
Форма неверна, потому что она объединяет ключевые слова списка лямбда. Почти наверняка расширение должно быть
(progn
(defun foo (amp;key (x 1)) x)
(defun remote-foo (amp;key (x 1)) `(foo :x ,x))
(defun remote-defun-foo () '(defun foo (amp;key (x 1)) x))
'foo)
Я думаю, что сделать это правильно непросто: вам нужно что-то, что может взять лямбда-список и превратить его в соответствующий ему список аргументов, и я не думаю, что это существует в CL как предварительно настроенная вещь. Я уверен, что он существует как что-то, что кто-то написал, однако я просто не знаю, где.
В качестве примера того, почему это не просто, рассмотрим это
(define-remote foo (amp;key (x 1 xp) ...))
Теперь remote-foo
, вероятно, необходимо
(defun remote-foo (amp;key (x 1 xp))
(if xp
`(foo :x ,x)
'(foo)))
Один из способов справиться с этим — просто запретить ключевые слова из лямбда-списка:
(defmacro define-remote (name (amp;rest args) amp;body decls/forms)
(dolist (a args)
(when (member a lambda-list-keywords)
(error "can't hack lambda list keywords in ~A, sorry" args)))
(let ((remote-name (intern (format nil "REMOTE-~A" (symbol-name name))))
(remote-defun-name (intern (format nil "REMOTE-DEFUN-~A"
(symbol-name name))))
(def `(defun ,name (,@args) ,@decls/forms)))
`(progn
,def
(defun ,remote-name (,@args) `(,',name ,,@args))
(defun ,remote-defun-name ()
',def)
',name)))
Эта версия макроса ограничена, но верна.