当我使用省略号递归时,为什么我的代码会冻结?

时间:2016-10-12 03:54:30

标签: recursion racket

它看起来不是无限递归,因为我有一个基本情况,每个递归调用都采用较小的列表arg1

#lang racket

(define a '("Hat" "Shoes"))

(define b '("Coat" "Hat"))

(define c '("Shirt" "Pants"))

(define-syntax func
  (syntax-rules ()
    ((func arg1 arg2 ... n)
     (if (or (empty? arg1) (empty? arg2) ...)
         empty
         (if (or (member (first arg1) arg2) ...)
             (cons (string-append n "." (first arg1)) (func (rest arg1) arg2 ... n))
             (cons (first arg1) (func (rest arg1) arg2 ... n))
             )
         )
     )
    )
  )

(func a b c "prefix")

2 个答案:

答案 0 :(得分:1)

您定义了一个宏func

然后使用宏:(func a b c "prefix")。 使用宏时,宏扩展器会查找定义并将使用与输入模式匹配,并使用模板生成扩展。

此处(func a b c "prefix")(func arg1 arg2 ... n)匹配。 所以arg1 = a, arg2 = (b c), and n = "prefix"

现在使用模板。在模板里面我发现:

 (func (rest arg1) arg2 ... n))

让我们填写:

 (func (rest a) b c "prefix"))

当然,模板的其余部分也已填写。 现在因为宏的输出使用了func宏扩展器 现在需要扩展新用途:(func (rest a) b c "prefix"))。 该扩展的输出将包含(func (rest (rest a)) b c "prefix"))(以及更多)。宏扩展器现在扩展该表达式并获得(func (rest (rest (rest a))) b c "prefix"))

问题不在于func使用func,而是func的参数大小并没有减少。它表明在编写递归宏时必须小心。

答案 1 :(得分:1)

不要为此使用宏。宏应该用于生成代码,但这里没有必要这样做。 特别对使用扩展到自己的宏的警惕 - 他们会递归扩展!正如soegaard所指出的那样,在这种情况下,你的宏无限扩展,产生无限量的代码。这显然非常糟糕。

请记住,宏在编译时运行,而不是在运行时运行,因此宏的扩展不可能依赖于运行时值。出于这个原因,你的“基本情况”在宏的上下文中没有意义:从宏的角度来看,没有必要停止扩展。

你想要的是一个普通的可变参数函数。在Racket / Scheme中执行此操作的语法如下所示:

(define (f . args)
  ; do something with args
  )

在上面的示例中,args将绑定到包含提供给f的所有参数的列表。您可以使用它来实现func作为函数。如果您还想要syntax-rules提供的更具声明性的模式匹配功能,则应该查看match