为什么 `rest` return `'(())` 而不是球拍中的 `'()`

Why does `rest` return `'(())` instead of `'()` in racket

我看到了这个关于如何使用可变过程来实现任意数量函数的组合的程序

(define compose-n
  (lambda fs
    (cond
      [(null? fs) identity]
      [else (lambda (x)
              ((first fs) ((apply compose-n (rest fs)) x)))])))

但是,当我删除最后一行的 apply 并手动执行应用程序时,

(define removed-compose-n
  (lambda fs
    (cond
      [(null? fs) identity]
      [else (lambda (x)
              ((first fs) ((removed-compose-n (rest fs)) x)))])))

这开始循环并消耗所有内存,使我的计算机崩溃。此外,当我尝试打印 fs 以查看发生了什么时

(define removed-compose-n
  (lambda fs
    (printf "fs : ~s\n" fs)
    (cond
      [(null? fs) identity]
      [else (lambda (x)
              ((first fs) ((removed-compose-n (rest fs)) x)))])))

我很惊讶地看到

fs : (#<procedure:id>)
fs : (())
fs : (())
fs : (())
fs : (())
fs : (())
fs : (^C                      
; user break [,bt for context]

这向我暗示 (rest fs) 的计算结果为 '(()),这就是导致 removed-compose-n 循环的原因,但是 compose-n 摆脱了这个,因为 [=21] =] 与 (compose-n '()) 相同,它匹配 recursion/first cond 子句的基本情况。

为什么 (rest fs) 计算 '(()) 而不是 '(),导致 removed-compose-n 循环?或者是我错过了完全循环其他内容的原因?

考虑一个简单的案例,您将 removed-compose-n 应用于 add1:

(removed-compose-n add1)

函数体内的 fs 的计算结果为 (list add1)。由于它不是 '(),我们评估第二个 cond 子句的 RHS。

(lambda (x)
  ((first (list add1)) ((removed-compose-n (rest (list add1))) x)))

正在评估 firstrest

(lambda (x)
  (add1 ((removed-compose-n '()) x)))

当我们评估以下子表达式时:

(removed-compose-n '())

fs 的值为 (list '()),与 '(()) 相同。这是无限循环,因为下一个应用程序仍将是 (removed-compose-n '())

使用apply“拼接”参数。第二个例子传入列表本身而不拼接它的元素。

我终于明白发生了什么,我忘记了可变过程语义。

问题不是 rest 返回 '(())rest确实returns'()。然而,像 removed-compose-n 这样的可变参数过程将它们的参数收集到一个列表中(实际上,这就是实现可变性的方式,因为列表的长度是不固定的)。因此,例如,对于应用程序 (removed-compose-n identity add1 sub1)fs 的值将是 '(identity add1 sub1),而对于应用程序 (removed-compose-n identity)fs 的值将是 '(identity).同样,对于应用程序 (removed-compose-n '())fs 的值将是 '(()),这会导致循环。