问题描述
起初,我希望它很简单-鉴于它是Common Lisp,它具有所有语言中所有宏功能中最强大的功能。但是现在,八个小时后,我的背面出现了逗号和类似的引号,而我离解决方案还很遥远。
(defmacro remote (name arg-names &body body) ...)
它应该生成(我在下面的文本中使用“ foo”作为名称参数):
-
(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 &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)))
此宏版本有限,但正确。