在解释如何将数字转换为列表的答案中,number->list过程定义如下:
(define (number->list n)
(let loop ((n n)
(acc '()))
(if (< n 10)
(cons n acc)
(loop (quotient n 10)
(cons (remainder n 10) acc)))))
Run Code Online (Sandbox Code Playgroud)
这里使用" 命名let ".我不明白这个命名let是如何起作用的.
我看到定义了一个循环,其中变量n等于n,并且变量acc等于空列表.然后,如果n小于10 n则与acc一致.否则,"循环"应用n等于n/10和acc等于n/ 10 的剩余部分和先前累积的东西的缺点,然后调用自身.
我不明白为什么loop被称为循环(什么是循环?),它如何自动执行和调用自身,以及它将如何实际添加每个数字乘以其适当的乘数以在基数10中形成数字.
我希望有人可以对程序和上述问题发表意见,以便我能更好地理解它.谢谢.
Chr*_*ung 12
命名背后的基本思想let是它允许您创建一个可以调用自身的内部函数,并自动调用它.所以你的代码相当于:
(define (number->list n)
(define (loop n acc)
(if (< n 10)
(cons n acc)
(loop (quotient n 10)
(cons (remainder n 10) acc))))
(loop n '()))
Run Code Online (Sandbox Code Playgroud)
希望您更容易阅读和理解.
那么,您可能会问为什么人们倾向于使用命名let而不是定义内部函数并调用它.这与人们使用(未命名)的理由相同let:它将两个步骤(定义一个函数并调用它)转换为一个方便的形式.
它被称为循环,因为函数在尾部位置调用自身.这称为尾递归.使用尾递归,递归调用直接返回给调用者,因此不需要保持当前的调用框架.您可以根据需要多次执行尾递归,而不会导致堆栈溢出.这样,它就像循环一样工作.
如果您想了解有关命名let及其工作原理的更多信息,我写了一篇关于它的博客文章.(但是你不需要阅读它来理解这个答案.如果你好奇,它就在那里.)
一个正常的let用法可以被认为是一个匿名过程调用:
(let ((a 10) (b 20))
(+ a b))
;; is the same as
((lambda (a b)
(+ a b))
10
20)
Run Code Online (Sandbox Code Playgroud)
命名let只是将该过程绑定到过程范围内的名称,以便它等于单个过程letrec:
(let my-chosen-name ((n 10) (acc '()))
(if (zero? n)
acc
(my-chosen-name (- n 1) (cons n acc)))) ; ==> (1 2 3 4 5 6 7 8 9 10)
;; Is the same as:
((letrec ((my-chosen-name
(lambda (n acc)
(if (zero? n)
acc
(my-chosen-name (- n 1) (cons n acc))))))
my-chosen-name)
10
'()) ; ==> (1 2 3 4 5 6 7 8 9 10)
Run Code Online (Sandbox Code Playgroud)
请注意,letrect 的主体只是对命名过程求值,因此该名称不在第一次调用的环境中。因此你可以这样做:
(let ((loop 10))
(let loop ((n loop))
(if (zero? n)
'()
(cond n (loop (- n 1))))) ; ==> (10 9 8 7 6 5 4 3 2 1)
Run Code Online (Sandbox Code Playgroud)
该过程loop仅在 let 主体的环境中,并且loop在第let一次评估时不会隐藏变量。
在您的示例中,名称loop只是一个名称。在 Scheme 中,每个循环最终都是通过递归完成的,但通常在尾递归时使用该名称,因此是迭代过程。