为什么不让`let`用于命名内部递归过程?

Sha*_*nce 3 lambda scheme factorial let

考虑以下函数来实现计算阶乘:[1]

(define fac-tail
  (lambda (n)
    (define fac-tail-helper
      (lambda (n ac)
        (if (= 0 n)
            ac
            (fac-tail-helper (- n 1) (* n ac)))))
    (fac-tail-helper n 1)))
Run Code Online (Sandbox Code Playgroud)

我试图用let内部定义重写:

(define fac-tail-2
  (lambda (n)
    (let ((fac-tail-helper-2
            (lambda (n ac)
              (if (= 0 n)
                  ac
                  (fac-tail-helper-2 (- n 1) (* n ac))))))
    (fac-tail-helper-2 n 1))))
Run Code Online (Sandbox Code Playgroud)

define时间没有错误,但执行结果为:

#;> (fac-tail-2 4)
Error: undefined variable 'fac-tail-helper-2'.
{warning: printing of stack trace not supported}
Run Code Online (Sandbox Code Playgroud)

如何使let版本有效?

方案版本是SISC v 1.16.6

[1]基于factorialSICP第1.2.1节的迭代版本http://mitpress.mit.edu/sicp/full-text/book/book-ZH-11.html#%_sec_1.2.1

Nor*_*sey 8

如何让let版本工作?

letrec而不是let.


Sha*_*nce 6

R. Kent Dvbvig说:


事实上,let表达式是根据lambda和过程应用定义的语法扩展,它们都是核心句法形式.一般来说,任何形式的表达

(let ((var expr) ...) body1 body2 ...) 
Run Code Online (Sandbox Code Playgroud)

相当于以下内容.

((lambda (var ...) body1 body2 ...)
 expr ...)" [1]
Run Code Online (Sandbox Code Playgroud)

这意味着fac-tail-2相当于:

(define fac-tail-2
  (lambda (n)
    ((lambda (fac-tail-helper-2)
       (fac-tail-helper-2 n 1)) ;; <== scope where fac-tail-helper-2 is visible.
     (lambda (n ac) ;; this lambda is assigned to fac-tail-helper-2
       (if (= 0 n)
           ac
           (fac-tail-helper-2 (- n 1) (* n ac))))))) ;; <=== problem
Run Code Online (Sandbox Code Playgroud)

并且很明显,问题是fac-tail-helper-2名称在lambda上面突出显示的主体中作为参数可见,但不是lambda指定给参数的名称fac-tail-helper-2.

[1]第2.5节,方案编程语言的 "Lambda表达式" ,第4版http://scheme.com/tspl4/start.html#SECTGSLAMBDA