让入方案的语法



在我找到的一个例子中,我无法理解let的这种用法。我正在使用鸡肉计划。

(let loop ()
(print "hello world")
(loop)
)

这是一个简单的无限循环,它递归地称呼自己,我无法理解的是语法。我知道第一个参数必须是((<var[1]> <value[1]>)...(<var[n]> <value[n]))对的列表,其他参数是 let 的主体。 那么,为什么这个片段有效?

这是一个命名的let,它是帮助程序过程的简写,通常用于使用递归进行循环,其参数随着递归的进行而前进(尽管在您的代码中没有使用任何参数)。例如,此过程:

(define (test)
(let loop ((i 5))
(cond ((<= i 0) 'ok)
(else (print i)
(loop (- i 1))))))

。相当于这个:

(define (test)
(define (loop i)
(cond ((<= i 0) 'ok)
(else (print i)
(loop (- i 1)))))
(loop 5))

现在你看到问题中的代码片段与编写以下内容相同:

(define (loop)
(print "hello world")
(loop))
(loop)

另请注意,"loop"这个名字只是一个约定,你不妨把它命名为"iter"或"helper"或任何其他你喜欢的东西,这并不重要。

它之所以有效,是因为您关于第一个"参数"需要成为绑定列表的陈述是错误的。let的语法是这样的:

(let name ((binding expression) ...)
body ...)

该名称是可选的。可以有零个或多个绑定。 宏如何看待它是否命名取决于这样一个事实,即绑定需要是一个列表,而名称绝对需要是一个标识符。如果没有名称,它与:

((lambda (binding ...)
body ...)
expression ...)

但是,名称变为:

((letrec ((name (lambda (binding ...)
body ...)))
name)
expression ...)

当然,letrec是根据let来定义的:

(letrec ((name expression) ...)
body ...)
; ===
(let ((name 'undefined) ...)
(let ((tmp expression) ...)
(set! name tmp) ...)
body ...)

从而使上面的命名 let 变成这样:

(((lambda (name)
((lambda (tmp) (set! name tmp)) (lambda (binding ...) body ...))
name)
'undefined)
expression ...)

请注意名称未绑定在首次调用的帧处的技巧。它被返回并立即调用。因此,对于表达式,您实际上可以从自由变量中计算name,而不会命名let干扰这一点:

(let ((name "sylwester"))
(let name ((cur (list name))
(n 2))
(if (zero? n)
cur
(name (append cur cur) (- n 1)))))
; ==> ("sylwester" "sylwester" "sylwester" "sylwester")

使用绑定define阴影:

(let ((name "sylwester"))
(define (name cur n) 
(if (zero? n)
cur
(name (append cur cur) (- n 1))))
(name (list name) 2))
; ==> (#<proc> #<proc> #<proc> #<proc>)

在 R6RS 报告中,let的语法没有演示更复杂的命名let,而是在报告的其他位置添加了对其描述的引用。这可能是因为当您只需要本地绑定时,命名let可能会让人不知所措和混乱。这个和那个define是两个不同的东西,而不是 Scheme 中最令人困惑的部分,所以如果这些语言实际上有不同的名称而不是记录不同的地方,那么对于初学者来说,这门语言可能会更容易掌握。

相关内容

  • 没有找到相关文章

最新更新