在宏中写入 `loop ... collect` 时出现问题

Issues when write `loop ... collect` in macro

伙计们。

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

下面的代码是我今天下午写的。但它并没有按照我的目的工作。

(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))loop collect 给我一个奇怪的答案。

为什么会这样?我有什么方法可以解决这个问题吗?

您正在改变文字数据(引用)。如果您同意在该循环期间列表 (+ 1 2),绑定到 exp,在每次迭代中都是相同的,并且您在每次迭代中改变第二个元素,那么很容易想象一个列表收集了相同的列表 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)

如果你想要一个新的 consed 列表,那么 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))))