Определение макроса, который определяет набор функций и вызовов функций в Common Lisp

#macros #common-lisp

#макросы #common-lisp

Вопрос:

Сначала я ожидал, что это будет просто — учитывая, что это Common Lisp с самым мощным из всех макросов всех языков. Но теперь, 8 часов спустя, у меня есть запятые и квазикавычки, выходящие из моей задней части, и я ни на шаг не ближе к решению.

Мне нужен макрос, который производит 3 функции. Его подпись выглядит следующим образом:

 (defmacro remote (name arg-names amp;body body) ...)
  

И он должен генерироваться (я использую «foo» в качестве аргумента name в тексте ниже):

  1. (defun foo (,arg-names) ,@body ) (просто прямая реализация foo), которая может быть вызвана как (foo arg1 arg2 ... ) .

  2. (defun remote-foo (args) ... который может быть вызван как (remote-foo arg1 arg2 ...) и возвращает заключенный в кавычки вызов. (foo arg1 arg2 ...)

  3. (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)))
  

Эта версия макроса ограничена, но верна.