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

问题描述

起初,我希望它很简单-鉴于它是Common Lisp,它具有所有语言中所有宏功能中最强大的功能。但是现在,八个小时后,我的背面出现了逗号和类似的引号,而我离解决方案还很遥远。

我需要一个产生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 ...)调用并返回引用的调用(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 &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

第二种形式(您想使用已创建函数的参数来创建带引号的调用)是第二种形式(该问题的较早版本由于我误解了这个问题而出错)必须摆弄一些嵌套的反引号。

我发现有助于理解这一点的一种方法是用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 list关键字。几乎可以肯定,扩张应该是

(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列表并将其转换为与其对应的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)))

此宏版本有限,但正确。