我正在使用Racket宏实现while循环。我的问题是,有人可以向我解释为什么下面的代码会产生一个无限的宏扩展循环吗?递归while调用之前的最后一个语句-body-将x的值减1,因此您会认为我们将朝着x等于0的条件迈进。但是我显然缺少了一些东西。预先谢谢你!
(let ([x 5])
(while (> x 0)
(displayln x)
(set! x (- x 1))))
(define-syntax while
(syntax-rules ()
((while c var body)
(if
c
(begin
var
body
(while c var body))
(void)))))
答案 0 :(得分:3)
宏不是功能。它们生成代码,在编译时应用,并且对运行时值一无所知。这意味着即使您编写此代码:
(define-syntax-rule (some-macro)
(displayln "hello!"))
(when #f
(some-macro))
…some-macro
的使用将仍然扩展,并且程序将转换为此:
(when #f
(displayln "hello!"))
使用宏时,了解非常重要:宏实际上是代码替换工具。扩展宏时,该宏的使用实际上将替换为该宏生成的代码。这可能会导致递归宏出现问题,因为如果您不小心,宏扩展将永远不会终止。考虑以下示例程序:
(define-syntax-rule (recursive-macro)
(when #f
(displayln "hello!")
(recursive-macro)))
(recursive-macro)
在单个宏扩展步骤之后,(recursive-macro)
的使用将扩展为:
(when #f
(displayln "hello!")
(recursive-macro))
现在,如果recursive-macro
是一个函数,那么它出现在when
形式内的事实将无关紧要-它永远不会被执行。但是recursive-macro
不是一个函数,它是一个宏,并且它将被扩展,而不考虑分支永远不会在运行时被使用的事实。这意味着在第二个宏扩展步骤之后,程序将被转换为此:
(when #f
(displayln "hello!")
(when #f
(displayln "hello!")
(recursive-macro)))
我认为您可以看到前进的方向。嵌套的recursive-macro
使用将永远不会停止扩展,并且程序将迅速无限制地增长。
您可能对此不满意。鉴于该分支将永远不会被采用,为什么扩展器会愚蠢地继续扩展?好吧,recursive-macro
是一个非常愚蠢的宏,因为编写一个扩展为永远不会执行的代码的宏并不会很有用。相反,假设我们稍微修改了recursive-macro
的定义:
(define-syntax-rule (recursive-macro)
(when (zero? (random 2))
(displayln "hello!")
(recursive-macro)))
现在很明显,扩展器不知道会执行多少次递归调用,因为行为是随机的,并且每次执行程序时生成的随机数都会不同。鉴于扩展是在编译时发生的,而不是在运行时发生的,因此扩展器尝试并考虑运行时信息只是没有意义。
这是您的while
宏的问题。您似乎期望while
的行为类似于函数调用,并且在未使用的运行时分支中递归使用while
不会被扩展。事实并非如此:宏在编译时扩展,而不考虑运行时信息。确实,您应该将宏扩展过程看作是一种转换,它生成的程序中没有任何宏作为输出,然后才执行。
牢记这一点,如何解决?嗯,在编写宏时,您需要把自己想像成是一个微型编译器:您的宏需要通过将输入代码转换为执行所需行为的代码来实现其功能,该代码使用完全定义简单的语言功能。在这种情况下,在Racket中实现循环的一种非常简单的方法是a named-let
loop,如下所示:
(let ([x 5])
(let loop ()
(when (> x 0)
(displayln x)
(set! x (- x 1))
(loop))))
这可以轻松实现您的while
构造:您只需要编写一个宏即可将while
转换为等效的命名为-let
:
(define-syntax-rule (while c body ...)
(let loop ()
(when c
body ...
(loop))))
这将达到您的期望。
当然,这个宏不是非常惯用的球拍。 cket子手更喜欢避免set!
和其他形式的变异,他们只会使用one of the built-in for
constructs来编写迭代而无需分配。不过,如果您只是尝试使用宏系统,那是完全合理的。