我正在尝试在Lisp中实现宏函数OR
我的尝试:
(defmacro or2 (test &rest args)
`(if ,test ,test (if (list ,@args) (or2 ,@args) nil)) )
但是,如果我用这样的东西测试:
(or2 (print 1) 2 )
1
1
1
使用默认OR
:
(or (print 1) 2)
1
1
我理解这是因为我的,test
条款开头的两个if
,但我不知道如何避免它。我怎么能避免应用两倍的测试效果?
答案 0 :(得分:2)
如果必须手动编码,你会如何解决副作用的问题?
(or2 (print 1) 2)
最有可能的是,你会这样做:
(let ((value (print 1)))
(if value value 2))
您需要定义一个包含第一个表达式值的局部变量,以便稍后您可以引用变量,而不是多次重新评估同一个表达式。
但是如果你在扩展代码的词汇上下文中已经有一个名为value
的变量怎么办?如果您引用其他2
而不是value
,该怎么办?此问题命名为变量捕获。
在Common Lisp中,使用GENSYM
引入一个新的符号,保证不会绑定到任何内容。
(let ((symbol (gensym)))
`(let ((,symbol ,test))
(if ,symbol ,symbol ...)))
(list ,@args)
以上内容与直接撰写,args
相同。
但是你混淆了宏扩展和执行时间。如果直接在代码中注入args
,它将被评估(很可能,这将作为一个错误的函数调用失败)。你想要的是测试宏扩展期间args
是否为非空。
此外,您应该首先测试表达式列表是否包含多个元素,以简化生成的代码。
粗略地说,您必须考虑以下情况:
(or2)
是nil
(or2 exp)
与exp
(or2 exp &rest args)
与以下内容相同,其中var
是一个新的符号:
`(let ((,var ,exp))
(if ,var ,var (or2 ,@args)))
答案 1 :(得分:1)
请使用macroexpand-1
:
(macroexpand-1 '(or2 (print 1) 2))
; ==> (if (print 1) (print 1) (if (list 2) (or2 2) nil)) ;
; ==> t
使用宏,您希望预期评估顺序,并且您希望表达式仅被评估一次。因此扩展应该是这样的:
(let ((tmp (print 1)))
(if tmp
tmp
(or2 2)))
tmp
应该是gensym
生成的符号。同样,当args
为nil
时,您应将or2
展开为仅test
:
(defmacro or2 (test &rest args)
(if (endp args)
test
(let ((tmp (gensym "tmp")))
`(let ((,tmp ,test))
(if ,tmp
,tmp
(or2 ,@args))))))
你可以make use of macros来简化这个:
(defmacro or2 (test &rest args)
(if (endp args)
test
(once-only (test)
`(if ,test
,test
(or2 ,@args)))))