在本页中:
4.6.1 平行绑定:let
4.6.2 顺序绑定:let*
4.6.3 递归绑定:letrec
4.6.4 命名的 let
4.6.5 多值绑定:let-valueslet*-valuesletrec-values

4.6 局部绑定

尽管 define 内部定义可用于局部绑定,然而 Racket 还提供了三种形式, 赋予了程序员比绑定更多的控制能力:letlet*letrec

4.6.1 平行绑定:let

+The Racket ReferenceLocal Binding: let, let*, letrec, ...一节中也提供了let的文档。

let 形式将多个标识符绑定到一些表达式的结果上以便在 let 的主体中使用:

(let ([id expr] ...) body ...+)

id 是“平行绑定”的,即没有任何 id 会作为右边的 expr 被绑定到某个 id 上。不过所有的 idbody 中均可用。 id 之间必须互不相同。

例如:
> (let ([me "Bob"])
    me)

"Bob"

> (let ([me "Bob"]
        [myself "Robert"]
        [I "Bobby"])
    (list me myself I))

'("Bob" "Robert" "Bobby")

> (let ([me "Bob"]
        [me "Robert"])
    me)

eval:3:0: let: duplicate identifier

  at: me

  in: (let ((me "Bob") (me "Robert")) me)

idexpr 无法看到其自己的绑定这一事实, 通常对于必须引用旧有值的包裹体来说很有用:

> (let ([+ (lambda (x y)
             (if (string? x)
                 (string-append x y)
                 (+ x y)))]) ; 使用原始的 +
    (list (+ 1 2)
          (+ "see" "saw")))

'(3 "seesaw")

有时,let 绑定的平行性质对于交换或重排一些绑定来说也很方便:

> (let ([me "Tarzan"]
        [you "Jane"])
    (let ([me you]
          [you me])
      (list me you)))

'("Jane" "Tarzan")

let 绑定的“平行性”并不意味着它隐含了并发求值。expr 会按照顺序求值,即便绑定过程会推迟到所有 expr 求值完毕。

4.6.2 顺序绑定:let*

+The Racket ReferenceLocal Binding: let, let*, letrec, ...一节中也提供了let*的文档。

let* 的语法与 let 相同:

(let* ([id expr] ...) body ...+)

不同之处在于每个 id 都可以在后续的 expr 中使用,body 亦同。此外,id 可以相同,其中最近的绑定是可见的。

例如:
> (let* ([x (list "Burroughs")]
         [y (cons "Rice" x)]
         [z (cons "Edgar" y)])
    (list x y z))

'(("Burroughs") ("Rice" "Burroughs") ("Edgar" "Rice" "Burroughs"))

> (let* ([name (list "Burroughs")]
         [name (cons "Rice" name)]
         [name (cons "Edgar" name)])
    name)

'("Edgar" "Rice" "Burroughs")

换句话说,let* 形式等价于嵌套的 let 形式,每层都只有一个绑定:

> (let ([name (list "Burroughs")])
    (let ([name (cons "Rice" name)])
      (let ([name (cons "Edgar" name)])
        name)))

'("Edgar" "Rice" "Burroughs")

4.6.3 递归绑定:letrec

+The Racket ReferenceLocal Binding: let, let*, letrec, ...一节中也提供了letrec的文档。

letrec 的语法也与 let 相同:

(letrec ([id expr] ...) body ...+)

let 的绑定只在 body 中可用,let* 的绑定可在任何之后的 expr 绑定中可用,而 letrec 的绑定对于所有其它 expr 来说均可用,即便前面的也是。换句话说, letrec 绑定是递归的。

letrec 形式中的 expr 最常用 lambda 形式来表示递归或互递归函数。

> (letrec ([swing
            (lambda (t)
              (if (eq? (car t) 'tarzan)
                  (cons 'vine
                        (cons 'tarzan (cddr t)))
                  (cons (car t)
                        (swing (cdr t)))))])
    (swing '(vine tarzan vine vine)))

'(vine vine tarzan vine)

> (letrec ([tarzan-near-top-of-tree?
            (lambda (name path depth)
              (or (equal? name "tarzan")
                  (and (directory-exists? path)
                       (tarzan-in-directory? path depth))))]
           [tarzan-in-directory?
            (lambda (dir depth)
              (cond
                [(zero? depth) #f]
                [else
                 (ormap
                  (λ (elem)
                    (tarzan-near-top-of-tree? (path-element->string elem)
                                              (build-path dir elem)
                                              (- depth 1)))
                  (directory-list dir))]))])
    (tarzan-near-top-of-tree? "tmp"
                              (find-system-path 'temp-dir)
                              4))

directory-list: could not open directory

  path: /var/tmp/systemd-private-5d92c99a9c9040dfa92e10b2e68

d4ce7-systemd-timesyncd.service-7iYVOn

  system error: Permission denied; errno=13

尽管 letrec 中的 expr 通常为 lambda 表达式, 然而它们实际上可以是任何表达式。表达式按顺序求值,在得到所有的值后, 它们会立即被关联到与其对应的 id。若某个 id 在其值就绪前被引用,那么就会触发一个错误,就像内部定义中的那样。

> (letrec ([quicksand quicksand])
    quicksand)

quicksand: undefined;

 cannot use before initialization

4.6.4 命名的 let

命名的 let 是一种递归或迭代的形式。它使用相同的语法关键字 let 进行局部绑定,但 let 之后的标识符(而非紧跟着的开括号)会触发不同的解析过程。

(let proc-id ([arg-id init-expr] ...)
  body ...+)

命名的 let 形式等价于

(letrec ([proc-id (lambda (arg-id ...)
                     body ...+)])
  (proc-id init-expr ...))

也就是说,命名的 let 只绑定在函数体中可见的函数标识符, 且它隐式地以某些初始表达式的值调用了函数。

例如:
(define (duplicate pos lst)
  (let dup ([i 0]
            [lst lst])
   (cond
    [(= i pos) (cons (car lst) lst)]
    [else (cons (car lst) (dup (+ i 1) (cdr lst)))])))
> (duplicate 1 (list "apple" "cheese burger!" "banana"))

'("apple" "cheese burger!" "cheese burger!" "banana")

4.6.5 多值绑定:let-valueslet*-valuesletrec-values

+The Racket ReferenceLocal Binding: let, let*, letrec, ...一节中也提供了多值绑定形式的文档。

define-values 在定义中绑定多个结果(见 多值与 define-values) 一样,let-valueslet*-valuesletrec-values 也按照同样的方式局部地绑定多个结果。

(let-values ([(id ...) expr] ...)
  body ...+)
(let*-values ([(id ...) expr] ...)
  body ...+)
(letrec-values ([(id ...) expr] ...)
  body ...+)

每个 expr 产生的值的数量都必须与 id 相对应。 这些形式的绑定规则与不带 -values 的形式相同: let-valuesid 只在 body 中绑定, let*-valuesid 可在后面从句的 expr 中绑定,而 letrec-valueid 可在所有的 expr 中绑定。

例如:
> (let-values ([(q r) (quotient/remainder 14 3)])
    (list q r))

'(4 2)