Common lisp 避免在lisp中实现宏函数的副作用

Common lisp 避免在lisp中实现宏函数的副作用,common-lisp,Common Lisp,我试图在Lisp中实现宏函数或 我的尝试: (defmacro or2 (test &rest args) `(if ,test ,test (if (list ,@args) (or2 ,@args) nil)) ) 但是,如果我用这样的方法进行测试: (or2 (print 1) 2 ) 1 1 1 而对于默认的或: (or (print 1) 2) 1 1 我知道这是因为我在if子句的开头有两个,test,但我不知道如何避免它。我如何避免应用两次测试效

我试图在Lisp中实现宏函数

我的尝试:

(defmacro or2 (test &rest args)   
   `(if ,test ,test (if  (list ,@args) (or2 ,@args) nil)) )
但是,如果我用这样的方法进行测试:

(or2 (print 1) 2 )

1 
1 
1
而对于默认的

(or (print 1) 2)

1 
1

我知道这是因为我在
if
子句的开头有两个
,test
,但我不知道如何避免它。我如何避免应用两次测试效果?

如果必须手动编写代码,您将如何解决副作用问题

(or2 (print 1) 2)
中间变量 很可能,您会这样做:

(let ((value (print 1)))
  (if value value 2))
您需要定义一个局部变量来保存第一个表达式的值,以便以后可以引用该变量,而不是多次重新计算同一个表达式

但是,如果在展开代码的词法上下文中已经有一个名为
value
的变量,该怎么办?如果您引用的不是
2
,而是另一个
,该怎么办?这个问题称为变量捕获

根森 在CommonLisp中,您引入了一个新的符号,它保证不会绑定到任何东西,使用

递归展开 上述操作与直接编写
,args
相同

但您混淆了宏扩展和执行时间。如果直接在代码中插入
args
,则将对其进行评估(很可能,这将作为错误的函数调用失败)。相反,您需要测试在宏扩展期间
args
是否为非null。 此外,您可能应该首先测试表达式列表是否包含多个元素,以简化生成的代码。 粗略地说,您必须考虑以下情况:

  • (or2)
    nil
  • (或2 exp)
    exp
  • (或2 exp&rest args)
    与以下相同,其中
    var
    是一个新符号:

    `(let ((,var ,exp))
       (if ,var ,var (or2 ,@args)))
    

请使用
宏扩展-1

(宏扩展-1'(or2(打印1)2))
; ==> (如果(打印1)(打印1)(如果(列表2)(或2)无));
; ==> T
对于宏,您希望计算顺序符合预期,并且希望表达式只计算一次。因此,扩张应该是这样的:

(or2 (print 1) 2 )

1 
1 
1
(let((tmp(打印1)))
(如果是tmp
tmp
(或2)
tmp
应该是由
gensym
生成的符号。另外,当
args
nil
时,您应该将
或2
展开为仅
测试

(defmacro or2(测试和rest参数)
(如果(endp args)
测试
let((tmp(gensym“tmp”))
`(let(,tmp,测试))
(如适用,tmp
,tmp
(或2,@args()()))
您可以简化此操作:

(defmacro or2(测试和rest参数)
(如果(endp args)
测试
(仅一次(测试)
`(如果有,测试
,测试
(or2,@args(()))

我已经累了好几个小时了;但我不知道如何将答案的所有要素组合在一起才能使其发挥作用:(@JonathP很抱歉听到这个消息。我想说西尔维斯特的回答给出了我心目中的那种代码。但是如果你被卡住了,你可以在你的问题中提供更多的细节,以及你目前掌握的代码,我们可以看到是什么阻碍了你。我尝试了几种方法(没有全部保留副本).我添加了可选的测试和一些打印,以了解发生了什么。这是我最新的跛脚尝试
`(let ((,var ,exp))
   (if ,var ,var (or2 ,@args)))