2013-10-15 60 views
2

这里是我的宏定义:未定义的变量,而unquoting宏观

*(defmacro run-test (test) 
    `(format t "Run test ~a ... ~a" ',test ,test)) 
*(run-test (= 1 1)) 
Run test (= 1 1) ... T 
NIL 

一切正常,现在,我已经定义了一个第二个宏(运行多个测试):

*(defmacro run-tests (&body body) 
    `(loop for tc in ',body 
     do (run-test tc))) 
* (run-tests (= 2 (1+ 1)) (= 1 1)) 
Run test TC ... (= 2 (1+ 1) Run test TC ... (= 1 1) 

这个结果并不是我想要的,我希望tc的每个值都被sexp替换,并且在运行测试中评估该值。我试着用

  do (run-test ,tc) 

更换线

  do (run-test tc) 

但这发出错误信号,

未定义的变量:TC

我怎样才能改变这种获得正确的结果?

回答

10

看看例如(run-tests (= 1 1))

(loop for tc in '((= 1 1)) do (run-test tc)) 

正如你看到的,代码试图调用(run-test tc)。但run-test在窗体上运行;当您通过一个包含表单的变量时它不起作用。

如果将代码更改为(run-test ,tc),则在宏展开时尝试引用tc变量,但它仅在运行时绑定。

一个解决办法是在宏扩展的时间做更多:

(defmacro run-tests (&body body) 
    `(progn ,@(loop for tc in body collect `(run-test ,tc)))) 
+0

我喜欢使用'loop'来构造宏展开的可能解决方案。一般来说,手工编写至少一次代码是宏应扩展到的_desired_代码的有用方法。在这种情况下,它应该是'(progn(run-test form1)(run-test form2)...)',并且从这个角度看,'loop'显然是获得它的好方法。 –

+0

非常好的解决方案。我再次查看了LISP宏的细节。我错了解宏观扩展阶段和运行阶段。 –

0

只是作为一个练习。您可以摆脱宏:

(defun run-test (test) 
    (format t "~%Run test ~a ... ~a" test (eval test))) 

(defun run-tests (tests) 
    (mapc 'run-test tests) 
    (values)) 

* (run-tests '((= 2 (1+ 1)) 
       (= 1 1))) 
+0

是的,实际上,作为Python播放器,编写函数对我来说很简单。这仅仅是我学习LISP的宏。不管怎样,谢谢。 –

+0

@WuLi不要使用宏,只要一个函数就足够了。如果你绝对想要这样做,你可能要考虑身体会返回什么,它是一个函数(并且对于嵌套,这适用于想要扩展发生的站点的确切形式,而忽略发生哪些变量绑定发生为了生效,毕竟宏是源代码转换)。 – Vatine

+0

@Vatine:是的,“从不使用函数就足够的宏”是一个很好的经验法则,但“eval是邪恶的”也是如此。我认为一个宏是一个很好的方法来完成这个任务,如果做得对,并且具有不需要参数引用的优点。但是确实,'eval'方法更容易正确,而且由于测试函数旨在在开发过程中使用,而不是程序的实际运行时间,因此在这里稍微可以接受。 –