Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/loops/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Loops 在球拍中使用宏中断While循环_Loops_Macros_Racket - Fatal编程技术网

Loops 在球拍中使用宏中断While循环

Loops 在球拍中使用宏中断While循环,loops,macros,racket,Loops,Macros,Racket,我正在使用Racket宏实现while循环。我的问题是,有人能给我解释一下为什么下面的代码会产生一个无限的宏扩展循环吗?递归while调用体之前的最后一条语句将x的值递减1,因此您可能认为我们将朝着x等于0的条件前进。但我显然错过了什么。提前谢谢你 (let ([x 5]) (while (> x 0) (displayln x) (set! x (- x 1)))) (define-syntax while (syntax-rules () (

我正在使用Racket宏实现while循环。我的问题是,有人能给我解释一下为什么下面的代码会产生一个无限的宏扩展循环吗?递归while调用体之前的最后一条语句将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)))))

宏不是函数。它们生成代码,在编译时应用,对运行时值一无所知。这意味着,即使您写下以下内容:

(define-syntax-rule (some-macro)
  (displayln "hello!"))

(when #f
  (some-macro))
…某些宏的使用仍将扩展,程序将转换为以下宏:

(when #f
  (displayln "hello!"))
(let ([x 5])
  (let loop ()
    (when (> x 0)
      (displayln x)
      (set! x (- x 1))
      (loop))))
在使用宏时,理解这一点非常重要:宏实际上是代码替换工具。当宏展开时,宏的使用将被宏生成的代码替换。这可能会导致递归宏出现问题,因为如果不小心,宏扩展将永远不会终止。考虑这个示例程序:

(define-syntax-rule (recursive-macro)
  (when #f
    (displayln "hello!")
    (recursive-macro)))

(recursive-macro)
在单个宏扩展步骤之后,
(递归宏)
的使用将扩展到:

(when #f
  (displayln "hello!")
  (recursive-macro))
(when #f
  (displayln "hello!")
  (when #f
    (displayln "hello!")
    (recursive-macro)))
现在,如果
递归宏
是一个函数,那么当窗体出现在
中时,它出现在
中这一事实将无关紧要,它将永远不会被执行。但是递归宏
不是一个函数,它是一个宏,它将被扩展,而不管在运行时永远不会执行分支这一事实。这意味着在第二个宏扩展步骤后,程序将转换为:

(when #f
  (displayln "hello!")
  (recursive-macro))
(when #f
  (displayln "hello!")
  (when #f
    (displayln "hello!")
    (recursive-macro)))
我想你能看到这是怎么回事。使用的嵌套的
递归宏
将永远不会停止扩展,程序将迅速变得无限大


你可能对此不满意。既然分支永远不会被取下,为什么扩展器会愚蠢地继续扩张?嗯,
recursive macro
是一个非常愚蠢的宏,因为编写一个扩展为永远不会执行的代码的宏是没有用的。相反,假设我们稍微修改了递归宏的定义:

(define-syntax-rule (recursive-macro)
  (when (zero? (random 2))
    (displayln "hello!")
    (recursive-macro)))
现在很明显,扩展器无法知道递归调用将执行多少次,因为行为是随机的,并且每次程序执行时生成的随机数都会不同。考虑到扩展是在编译时进行的,而不是在运行时进行的,因此扩展程序尝试解释运行时信息是没有意义的

这就是您的
while
宏的问题所在。您似乎期望
while
的行为类似于函数调用,并且在未执行的运行时分支中递归使用
while
不会被扩展。但事实并非如此:宏是在编译时展开的,与运行时信息无关。事实上,您应该将宏扩展过程视为一种转换,它生成一个程序,而该程序中没有任何宏作为输出,然后才执行


考虑到这一点,你如何修复它?在编写宏时,您需要将自己看作是在编写一个小型编译器:您的宏需要通过将输入代码转换为执行所需行为的代码来实现其功能,这些行为完全是使用更简单的语言特性定义的。在这种情况下,在球拍中实现循环的一种非常简单的方法如下:

(when #f
  (displayln "hello!"))
(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))))
这将满足您的期望


当然,这个宏不是很惯用。敲诈勒索者更喜欢避开
set和其他形式的变异,他们只需要编写迭代而不需要赋值。不过,如果您只是在试验宏系统,这是完全合理的。

您的扩展包含您最初扩展的表达式,因此每次扩展都至少需要一个新的表达式。您需要使表达式本身不包含在结果中。也许名为
?非常感谢您的详细回答。我有一个后续问题。根据您的解释,公平地说Racket中的宏永远不应该递归使用,因为它们总是表现出这种行为(即在编译时无限扩展)?@AlexanderNenartovich有递归宏是可以的,但就像递归函数一样,它们需要有一个基本情况,这个基本情况需要在编译时达到。例如,
cond
最合乎逻辑的实现是递归地将其转换为嵌套的
if
形式,因为每个递归步骤处理一个子句,所以基本情况是空的
(cond)
form。是否可以使用递归宏重新编写我的尝试,并添加可在编译时访问的基本情况?只是好奇而已。@AlexanderNenartovich不,因为循环的基本情况是在运行时,直到程序执行时,你才知道循环应该运行多少次。但是使用
cond
,您可以在编译时准确地知道有多少子句(因为用户显式地编写它们)。换句话说,编写一个递归的
,而
宏将执行循环展开,您需要准确地知道提前进行了多少次迭代。再次感谢您!在您使用命名let编写的解决方案中,如果条件c为true,循环是否作为程序从最后一行跳转到并执行的主体的标签?这基本上是Racket写循环的方式吗?