我需要编写一个带有符号和列表的Common Lisp宏。该列表由两个元素的列表组成:符号和字符串,如下所示:
((X "foo") (Y "bar") (Z "qwerty"))
宏以递归方式工作,它搜索列表中的符号,如果找到symbol
则返回T
,否则返回NIL
。我写这段代码:
(defmacro already-exist (symbol my-list)
(cond ((null (eval my-list)) NIL)
((eql (caar (eval my-list)) symbol)
T)
(T `(already-exist symbol ,(cdr (eval my-list))))))
但问题在于递归部分。实际上,如果我尝试使用没有symbol
的列表作为第一个元素的一部分运行宏,我会收到错误。例如:
(defparameter listt '((X "foo") (Y "bar") (Z "qwerty")))
(already-exist Y listt)
我获得的错误是“非法函数调用”。我认为这是因为宏试图将Y
评估为函数调用。我怎么能解决这个问题?通常,在Common Lisp中编写递归宏的最佳方法是什么?
答案 0 :(得分:4)
我需要编写一个Common Lisp宏[...]宏以递归方式工作。
你并不真正需要一个宏,实际上如果你的值只在运行时知道,宏就无法解决问题。如果你希望它们不被评估,你需要引用符号。
...
<nav class="navbar navbar-fixed-top" style="margin-top: 2vw; margin-right: 3vw; position: fixed;"> <div class="container-fluid"> <ul class="nav navbar-nav navbar-right"> <li><a href="#" style="border-style: solid; border-width: 1px;"><span class="glyphicon glyphicon-search rt"></span> Search</a></li> <li><a class="qt" href="#"><span class="strike"><span class="glyphicon glyphicon-minus"></span> About Us</span></a></li> <li><a href="#"><span class="strike"><span class="glyphicon glyphicon-minus"></span> Education</span></a></li> <li><a href="#"><span class="strike"><span class="glyphicon glyphicon-minus"></span> Consumer Service</span></a></li> <li><a href="#"><span class="glyphicon glyphicon-menu-hamburger" style="border-radius: 50%; border-width: 1px; border-style: solid; padding: 5px; margin-top: -5px;"></span></a></li> </ul> </div> </nav> <div id="test" class="jumbotron" style:"padding-top:30px"> <h1>Twitter Bootstrap 3.0</h1> <p class="lead">Starter template with CSS and JS included.</p> <p><a class="btn btn-lg btn-primary" href="#fork">Fork this fiddle</a></p> </div>
...
从宏调用eval是一个很大的代码味道。你的宏正在处理代码,@import url('//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css');
body {
margin: 10px;
}
#test {
margin-top: 87px;
height: 2000px;
}
符号此处没有任何意义,除了它是一个符号。在(eval my-list)
绑定到有意义值的环境中,宏很可能 not 将被扩展(此外,list
在null词法环境中工作)。
可以使用递归扩展,但宏本身不是递归的:
list
Macroexpansion以固定点方式应用:从代码X0开始,将X1计算为X0的宏展开,并继续直到没有宏扩展为止。您的宏不会调用自身,宏扩展工具会在每次传递后经常调用它(这也是为什么在宏扩展期间无法建立动态绑定的原因,除非您手动调用eval
。)
您可以将宏宏扩展为也可以调用宏的代码。但是你必须要小心,它不是无条件的,否则你将会有一个无限的宏观扩张。
如果* (defmacro foo (x) (foo x))
; in: DEFMACRO FOO
; (FOO X)
;
; caught STYLE-WARNING:
; undefined function: FOO
;
; compilation unit finished
; Undefined function:
; FOO
; caught 1 STYLE-WARNING condition
STYLE-WARNING:
FOO is being redefined as a macro when it was previously assumed to be a function.
* (foo 3)
debugger invoked on a UNDEFINED-FUNCTION in thread
#<THREAD "main thread" RUNNING {100399C503}>:
The function COMMON-LISP-USER::FOO is undefined.
将是仅在运行时知道的值,则需要编写常规函数。基本上,你想做:
macroexpand
请参阅MEMBER
,MACROEXPAND
。
答案 1 :(得分:3)
实际错误是什么?
实际错误是:
CL-USER 3 > (already-exist Y listt)
Error: Illegal car (Y "bar") in compound form ((Y "bar") (Z "qwerty")).
所以你试图执行这个表达式,这是一个无效的Lisp代码:
((Y "bar") (Z "qwerty"))
。
错误1:
您的代码会获得您评估的my-list
符号。下次获取列表时,您不应该对其进行评估。
错误2:
您使用symbol
创建了一个表单,而不是symbol
的值。
尝试修复导致丑陋的解决方案
my-list
symbol
的值示例:
CL-USER 2 > (defmacro already-exist (symbol my-list)
(when (symbolp my-list)
(setf my-list (eval my-list)))
(cond ((null my-list)
NIL)
((eql (caar my-list) symbol)
T)
(T `(already-exist ,symbol ,(cdr my-list)))))
ALREADY-EXIST
CL-USER 3 > (defparameter listt '((X "foo") (Y "bar") (Z "qwerty")))
LISTT
CL-USER 4 > (already-exist Y listt)
T
CL-USER 5 > (already-exist A listt)
NIL
但它没有多大意义并且会产生新的问题。
编写调用eval
的递归宏调用的风格很差。 eval
无法获得词法绑定的值,因为评估不会在词法环境中进行。
使用功能
CL-USER 10 > (member 'y listt :key #'car)
((Y "bar") (Z "qwerty"))
这意味着 true 。
答案 2 :(得分:1)
想象一下,你试过了:
(defmacro expand (&rest elements)
(if (not (null (cdr elements)))
`(+ ,(car elements) ,(expand (cdr elements)))
(car elements)))
现在编译宏函数时,它会展开所有宏,以便在其中调用(expand (cdr elements))
..
(defmacro expand (&rest elements)
(if (not (null (cdr elements)))
`(+ ,(car elements) (+ (cdr elements) (+ (cdr elements) (+ (cdr elements) (+ (cdr elements) ...))))
(car elements)))
你看到了吗?现在假设您只是扩展第一部分而不是递归,而是使用expand
代替更简单的表达式:
(defmacro expand (&rest elements)
(if (not (null (cdr elements)))
`(+ ,(car elements) (expand ,@(cdr elements)))
(car elements)))
这完全不同,因为宏从不直接使用宏。但是,(expand 1 2 3)
会扩展为(+ 1 (expand 2 3))
,并且lisp会继续扩展宏,直到没有任何内容为止,只留下(+ 1 (+ 2 3))
没有递归
(defmacro expand (&rest elements)
(labels ((recfun (elements)
(if (not (null (cdr elements)))
`(+ ,(car elements) ,(recfun (cdr elements)))
(car elements))))
(recfun elements)))
它也不需要是本地定义的函数。通常我将大部分功能作为一个函数实现,并制作一个宏来延迟评估一些参数,让宏只调用函数:
(defun make-env-fun (names)
(mapcar (lambda (name) (cons name (symbol-function name))) names))
(defmacro make-env (&rest variables)
`(make-env-fun ',variables))
(make-env cons car cdr)
; ==> ((cons . #<system-function cons>)
; (car . #<system-function car>)
; (cdr . #<system-function cdr>))
所以宏存在,因为我不想做(make-env 'cons 'car 'cdr)
或(make-env '(cons car cdr))
。该宏仅修复了该问题,而不是该函数仍在进行的实际工作。
因此,为了解决您的问题,您需要一个允许(already-exist symbol ((symbol "bla")))
代替(already-exist-fun 'symbol '((symbol "bla")))
的宏。你看到了吗?