定义一个在 Common Lisp 中定义一组函数和函数调用的宏

Defining a macro which defines a set of functions and function calls in Common Lisp

起初,我希望它很简单 - 因为它是 Common Lisp,具有所有语言中最强大的宏功能。但是现在,8 小时后,我的背面出现了逗号和准引号,我离解决方案还差得很远。

我需要一个能产生 3 个函数的宏。它的签名如下所示:

(defmacro remote (name arg-names &body body) ...)

它应该生成(我在下面的文本中使用“foo”作为名称参数):

  1. (defun foo (,arg-names) ,@body )(只是 foo 的直接实现)可以调用为 (foo arg1 arg2 ... ).

  2. (defun remote-foo (args) ... 可以调用为 (remote-foo arg1 arg2 ...) 和 returns 引用调用。 (foo arg1 arg2 ...)

  3. (defun remote-defun-foo () ... 像这样调用时 (remote-defun-foo) returns 函数的引用定义 (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 &body body)
  `(progn
     (defun ,name ,arg-names ,@body) ;; 1.
     ))

我不确定 - 但对于 2. 和 3. 我似乎需要类似嵌套的准引号或我以前从未使用过的其他技巧。

正如所描述的,这并不难:

(defmacro define-remote (name (&rest args) &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

第二种形式(这个答案的早期版本出错了,因为我误读了这个问题)是第二种形式,你想在其中使用你创建的函数的参数创建一个带引号的调用,所以你必须 fiddle 关于嵌套反引号。

我发现一种有助于理解的方法是根据 list 重写第二种形式,然后记住

(list x y)

相同
`(,x ,y)

(事实证明,我不知道如何在 markdown 中将反引号插入内联代码。)

然而,所有这些都有一个严重的潜在问题:资料片中的第二种形式通常是错误的。考虑一下:

> (macroexpand '(define-remote foo (&key (x 1)) x))
(progn
  (defun foo (&key (x 1)) x)
  (defun remote-foo (&key (x 1)) `(foo ,&key ,(x 1)))
  (defun remote-defun-foo () '(defun foo (&key (x 1)) x))
  'foo)
t

remote-foo形式是错误的,因为它拼接了lambda列表关键字。几乎可以肯定扩展应该是

(progn
  (defun foo (&key (x 1)) x)
  (defun remote-foo (&key (x 1)) `(foo :x ,x))
  (defun remote-defun-foo () '(defun foo (&key (x 1)) x))
  'foo)

我认为要做到这一点并不简单:您需要一些可以接受 lambda-list 并将其转换为与之对应的 arglist 的东西,而且我认为 CL 中不存在预制的事物。我确定它作为某人写的东西存在,但是我只是不知道在哪里。

作为为什么这不简单的例子,考虑这个

(define-remote foo (&key (x 1 xp) ...))

现在 remote-foo 可能需要

(defun remote-foo (&key (x 1 xp))
  (if xp
      `(foo :x ,x)
    '(foo)))

处​​理此问题的一种方法是简单地禁止 lambda-list 关键字:

(defmacro define-remote (name (&rest args) &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)))

这个版本的宏是有限的,但是是正确的。