在宏中写`loop ... collect`时的问题

时间:2016-08-19 22:34:19

标签: macros lisp common-lisp clisp

今天我想编写sigma宏来计算灵活表达式输入的总和。

以下代码是我今天下午写的。但它不符合我的目的。

sudo

我希望它有效(defmacro sigma (exp ll) `(+ ,@(loop for i in ll collect (progn (setf (elt exp 1) i) (print exp) exp))) ) >>(pprint (macroexpand-1 '(sigma (+ 1 2) (2 3 4)))) >>(+ 2 2) (+ 3 2) (+ 4 2) (+ (+ 4 2) (+ 4 2) (+ 4 2)) ,但(+ (+ 2 2) (+ 3 2) (+ 4 2))给了我奇怪的答案。

为什么它会像这样工作?我有一些方法可以解决这个问题吗?

2 个答案:

答案 0 :(得分:6)

您正在改变文字数据(引用)。如果您同意在该循环期间,绑定到exp的列表(+ 1 2)在每次迭代中是相同的,并且您在每次迭代中改变第二个元素,很容易想象收集的列表相同的列表exp 3次将具有3个完全相同的元素,其中第二个元素的最后一个突变。

这绝不是宏中的一项功能。对所有引用数据进行变异可以产生这样的结果。标准规定结果将是未定义的,因此没有实现者需要解决这个问题,并且您会从特定实现的其他方面获得意外行为。

编译后的文件可能会将所有引用的数据合并为一个并且相同,这样代码中的'(+ 1 2)个其他位置也可能会受到此宏的影响。

要解决这个问题,请不要改变:

(defmacro sigma ((op r &rest rest) ll)
  `(+ ,@(loop :for i :in ll 
              :collect (list* op i rest))))

(macroexpand-1 '(sigma (+ 1 2) (2 3 4)))
; ==> (+ (+ 2 2) (+ 3 2) (+ 4 2))

关于这一点的好处是保证模板中至少有两个参数。

(macroexpand-1 '(sigma (x) (2 3 4)))
; ==> *** - SIGMA: (X) does not match lambda list element (OP R &REST REST)

答案 1 :(得分:3)

如果你想要一个新的列表,那么copy-list是一种方式:

(defmacro sigma (exp ll)
  `(+ ,@(loop for i in ll and exp1 = (copy-list exp)
              do (setf (second exp1) i)
              collect exp1)))

嵌套的反引号表达式也是可能的:

(defmacro sigma ((op arg0 &rest args) ll)
  (declare (ignore arg0))
  `(+ ,@(loop for i in ll collect `(,op ,i ,@args))))