以循环形式命名的let如何工作?

Erw*_*ers 8 scheme

在解释如何将数字转换为列表的答案中,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/10acc等于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及其工作原理的更多信息,我写了一篇关于它的博客文章.(但是你不需要阅读它来理解这个答案.如果你好奇,它就在那里.)


Syl*_*ter 7

一个正常的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 中,每个循环最终都是通过递归完成的,但通常在尾递归时使用该名称,因此是迭代过程。