避免在lisp

时间:2018-03-19 12:00:33

标签: common-lisp

我正在尝试在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,但我不知道如何避免它。我怎么能避免应用两倍的测试效果?

2 个答案:

答案 0 :(得分:2)

如果必须手动编码,你会如何解决副作用的问题?

(or2 (print 1) 2)

中间变量

最有可能的是,你会这样做:

(let ((value (print 1)))
  (if value value 2))

您需要定义一个包含第一个表达式值的局部变量,以便稍后您可以引用变量,而不是多次重新评估同一个表达式。

但是如果你在扩展代码的词汇上下文中已经有一个名为value的变量怎么办?如果您引用其他2而不是value,该怎么办?此问题命名为变量捕获

Gensym

在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生成的符号。同样,当argsnil时,您应将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)))))