2

在 Practical Common Lisp 的第 8 章“堵漏”中,我们定义了这个宏,并通过检查发现它泄漏了macroexpand-1

(defmacro do-primes ((var start end) &body body)
  `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
        (ending-value ,end))
       ((> ,var ending-value))
     ,@body))

由于此泄漏,以下看似无辜的 do-primes 调用无法正常工作:

(do-primes (ending-value 0 10) (print ending-value)) 这个也不是:

(let ((结束值 0)) (do-primes (p 0 10) (incf 结束值 p)) 结束值)

我可以macroexpand-1像这样使用它,我将这个宏命名为 do-primes4

(macroexpand-1 '(do-primes4 (ending-value 0 10)
  (print ending-value)))

(DO ((ENDING-VALUE (NEXT-PRIME 0) (NEXT-PRIME (1+ ENDING-VALUE)))
     (ENDING-VALUE 10))
    ((> ENDING-VALUE ENDING-VALUE))
  (PRINT ENDING-VALUE))

但是我不能在第二种形式上做同样的事情,其中​​宏调用是用 let

(macroexpand-1 '(let ((ending-value 0))
  (do-primes4 (p 0 10)
    (incf ending-value p))
  ending-value))

(LET ((ENDING-VALUE 0))
  (DO-PRIMES4 (P 0 10)
    (INCF ENDING-VALUE P))
  ENDING-VALUE)

它似乎实际上并没有对 do-primes4 进行宏扩展。我正在使用 SBCL。

4

3 回答 3

4

GNU Emacs / 史莱姆

c-c m-m在表格上使用。它调用代码步行者。结果:

(LET ((ENDING-VALUE 0))
  (BLOCK NIL
    (LET ((P (NEXT-PRIME 0)) (ENDING-VALUE 10))
      (DECLARE (IGNORABLE P ENDING-VALUE))
      (TAGBODY
    (GO #:G586)
       #:G585
    (TAGBODY (SETQ ENDING-VALUE (+ P ENDING-VALUE)))
    (PROGN (SETQ P (NEXT-PRIME (1+ P))) NIL)
       #:G586
    (IF (> P ENDING-VALUE)
            NIL
            (GO #:G585))
    (RETURN-FROM NIL (PROGN)))))
  ENDING-VALUE)
于 2021-11-21T21:17:59.600 回答
1

尽管正如 Rainer Joswig 所指出的那样,您需要一个 code-walker 来查看在这种情况下实际扩展是什么,而且macroexpand-1code macroexpandwalker 也不需要,但实际上您并不需要它来查看问题所在。以类似的形式

(let ((ending-value 0))
  (do-primes (p 0 10)
    (incf ending-value p))
  ending-value)

您可以do-primes在顶层展开表单,然后将其替换,以获得明显的效果:

(let ((ending-value 0))
  (do ((p (next-prime 0) (next-prime (1+ p))) (ending-value 10))
      ((> p ending-value))
    (incf ending-value p))
  ending-value)

现在很明显问题是什么:ending-value你正在增加的不是你认为的绑定。

那里有各种各样的 CL 代码步行者:我对他们不够熟悉,无法推荐一个。

于 2021-11-22T11:46:28.670 回答
1

有两个宏扩展函数,macroexpandmacroexpand-1http://clhs.lisp.se/Body/f_mexp_.htm。两者都展开宏,macroexpand-1做一个级别,macroexpand继续进行,直到它完全展开。两者都返回两个值:

  1. 扩张
  2. 指示发生扩展的标志

宏展开是这样的:

; macro expansion

(macroexpand-1 '(do-primes4 (ending-value 0 10)
   (print ending-value)))
(DO ((ENDING-VALUE (NEXT-PRIME 0) (NEXT-PRIME (1+ ENDING-VALUE)))
     (ENDING-VALUE 10))
((> ENDING-VALUE ENDING-VALUE))
  (PRINT ENDING-VALUE))

; flag
T

首先打印扩展,然后是标志 - 此处T显示发生了扩展。

您的第二个示例的问题是let宏扩展中的第一项。目前,您只是尝试扩展let表单,其主体返回不变:

(macroexpand-1 '(let ((ending-value 0)) (do-primes4 (ending-value 0 10)
                                               (print ending-value))))

(LET ((ENDING-VALUE 0))
   (DO-PRIMES4 (ENDING-VALUE 0 10)
      (PRINT ENDING-VALUE)))
NIL

该标志采用 value NIL,以表明没有发生扩展。

let表单扩展为自身,因为它不是带有标志的宏NIL

CL-USER> (macroexpand-1 '(let ((x 0))))
  (LET ((X 0)))
  NIL
于 2021-11-21T17:15:39.440 回答