在an answer中解释了如何将数字转换为列表,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)))))
这里有&#34; named let
&#34;用来。我不明白这个名为let
的工作原理。
我看到定义了一个循环,其中变量n
等于n
,变量acc
等于空列表。然后,如果n
小于10,则n
与acc一致。否则,&#34;循环&#34;应用n
等于n/10
和acc
等于n
/ 10剩余部分的缺点以及之前累积的内容,然后自行调用。
我不明白为什么loop
被称为循环(什么是循环?),它如何自动执行和调用自身,以及它将如何实际添加每个数字乘以其适当的乘数来形成基数为10的数字。
我希望有人能够对程序和上述问题发表意见,以便我能更好地理解它。感谢。
答案 0 :(得分:11)
命名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 '()))
希望您更容易阅读和理解。
然后,您可能会问为什么人们倾向于使用命名的let
而不是定义内部函数并调用它。它与人们使用(未命名)let
的理由相同:它将两个步骤(定义一个函数并调用它)转换为一个方便的形式。
它被称为循环,因为该函数在尾部位置中调用自身。这称为tail recursion。通过尾递归,递归调用直接返回给调用者,因此不需要保持当前的调用框架。您可以根据需要多次执行尾递归,而不会导致堆栈溢出。这样,它就像循环一样工作。
如果您想了解有关已命名的let
及其工作原理的更多信息,请I wrote a blog post about it。 (但是,你不需要阅读它来理解这个答案。如果你感到好奇,它就在那里。)
答案 1 :(得分:3)
正常let
用法可以被视为匿名过程调用:
(let ((a 10) (b 20))
(+ a b))
;; is the same as
((lambda (a b)
(+ a b))
10
20)
命名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)
请注意,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)
过程loop
仅在let的主体环境中,并且在首次评估loop
时不会影响变量let
。
在您的示例中,名称loop
只是一个名称。在Scheme中,每个循环最终都是通过递归完成的,但通常在尾递归时使用名称,因此也就是迭代过程。