将let转换为lambda后代码行为不符,求原因(基于R⁷RS与SICP)
从R⁷RS第69页了解到let可以替换为lambda,例如:(let ((a 10)) something) 等价于 ((lambda (a) something) 10)。于是将SICP中的这段代码:
(define new-withdraw (let ((balance 100)) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds"))))
改写为:
(define w-lam (lambda (balance) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds")) 100))
但在REPL中运行结果不同:(new-withdraw 80) 返回20,(w-lam 80) 返回100。请问遗漏了什么?
核心问题是你对let转lambda的语法理解有误,正确的等价转换需要把lambda表达式用括号包裹后立即传入参数,而非将参数写在lambda体的末尾。
原let表达式(let ((balance 100)) <body>)的等价lambda形式是((lambda (balance) <body>) 100):这里外层lambda被立即调用,传入100作为balance的初始值,调用后返回内部的取款逻辑lambda函数,这个内部函数会闭包住初始值为100的balance变量,后续调用时能正确更新这个变量。
你写的w-lam定义里,(lambda (balance) ... 100)是一个接受balance参数的函数,Scheme函数的返回值是体中最后一个表达式的结果,所以这个函数不管传入什么参数,都会返回100。当你调用(w-lam 80)时,只是把80传给了外层lambda的balance参数,内部的取款lambda仅被定义但从未被调用,最终返回的是末尾的100,完全没触发取款逻辑。
正确的改写应该是:
(define w-lam ((lambda (balance) (lambda (amount) (if (>= balance amount) (begin (set! balance (- balance amount)) balance) "Insufficient funds"))) 100))
这样外层lambda被立即调用,传入100初始化balance,返回内部的取款lambda函数,此时w-lam和new-withdraw完全等价,调用(w-lam 80)会返回20,后续调用也能正确更新balance的值。
内容的提问来源于stack exchange,提问作者gobbolo22

