지역 바인딩: let, let*, letrec, ...

지역 바인딩: let, let*, letrec, ...

let 계열은 값을 지역 변수에 바인딩하고 본문을 평가하는 Racket의 기본 골격이에요. 평가 순서와 바인딩 범위가 형태마다 조금씩 달라집니다.

출처: Racket Reference

본문

지역 바인딩에 대한 소개는 The Racket Guide의 Local Binding에서 다룹니다.

let

syntax

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

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

첫 번째 형태는 val-expr들을 왼쪽에서 오른쪽으로 평가하고, 각 id마다 새 위치(location)를 만든 뒤 그 값들을 위치에 넣습니다. 그런 다음 id들이 바인딩된 body들을 평가합니다. 마지막 body 표현식은 let 폼에 대해 꼬리 위치에 있습니다. id들은 bound-identifier=?에 따라 서로 달라야 합니다.

예시:

> (let ([x 5]) x)
5

> (let ([x 5])
    (let ([x 2]
          [y x])
      (list y x)))
'(5 2)

두 번째 형태는 보통 named let이라 불리며, init-expr들을 평가합니다. 그 결과 값들은 프로시저 (lambda (id ...) body ...+)의 적용에서 인자가 됩니다. 이때 proc-idbody 안에서 그 프로시저 자신에 바인딩됩니다.

예시:

> (let fac ([n 10])
    (if (zero? n)
        1
        (* n (fac (sub1 n)))))
3628800

let*

syntax

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

let과 같지만, val-expr들을 하나씩 평가해서 값이 나오는 즉시 각 id의 위치를 만듭니다. id들은 나머지 val-expr들과 body에 바인딩되며, id들이 서로 달라야 할 필요는 없습니다. 나중의 바인딩이 앞의 바인딩을 가립니다(shadow).

예시:

> (let* ([x 1]
         [y (+ x 1)])
    (list y x))
'(2 1)

letrec

syntax

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

val-expr를 왼쪽에서 오른쪽으로 평가한다는 점(포함)에서 let과 같지만, 모든 id의 위치가 먼저 생성되고, 모든 id가 모든 val-exprbody에 바인딩되며, 각 id는 해당 val-expr가 평가된 직후에 초기화됩니다. id들은 bound-identifier=?에 따라 서로 달라야 합니다.

초기화 전에 id를 참조하거나 할당하면 exn:fail:contract:variable이 발생합니다. id(즉 바인딩 인스턴스)에 값이 심볼인 'undefined-error-name 문법 속성이 있으면, 그 심볼이 id의 상징적 형태 대신 오류 보고에서 변수 이름으로 사용됩니다.

예시:

> (letrec ([is-even? (lambda (n)
                       (or (zero? n)
                           (is-odd? (sub1 n))))]
           [is-odd? (lambda (n)
                      (and (not (zero? n))
                           (is-even? (sub1 n))))])
    (is-odd? 11))
#t

package base의 6.0.1.2 버전에서 변경: 초기화되지 않은 id의 참조나 할당이 오류로 변경되었습니다.

let-values

syntax

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

let과 같지만, 각 val-expr가 해당 id의 개수만큼의 값을 만들어내야 하고, 그렇지 않으면 exn:fail:contract 예외가 발생합니다. 각 id마다 별도의 위치가 생성되며, 모두 body에 바인딩됩니다.

예시:

> (let-values ([(x y) (quotient/remainder 10 3)])
    (list y x))
'(1 3)

let*-values

syntax

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

let*과 같지만, 각 val-expr가 해당 id의 개수만큼의 값을 만들어내야 합니다. 각 id마다 별도의 위치가 생성되며, 모두 나중의 val-expr들과 body에 바인딩됩니다.

예시:

> (let*-values ([(x y) (quotient/remainder 10 3)]
                [(z) (list y x)])
    z)
'(1 3)

letrec-values

syntax

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

letrec과 같지만, 각 val-expr가 해당 id의 개수만큼의 값을 만들어내야 합니다. 각 id마다 별도의 위치가 생성되며, 모두 모든 val-exprbody에 바인딩됩니다.

예시:

> (letrec-values ([(is-even? is-odd?)
                   (values
                    (lambda (n)
                      (or (zero? n)
                          (is-odd? (sub1 n))))
                    (lambda (n)
                      (or (= n 1)
                          (is-even? (sub1 n)))))])
    (is-odd? 11))
#t

let-syntax

syntax

(let-syntax ([id trans-expr] ...) body ...+)

splicing-let-syntax도 함께 보세요.

idtrans-expr의 값으로 transformer 바인딩(Transformer Bindings 참고)을 만듭니다. trans-expr는 주변 문맥에 대해 phase level 1에서의 표현식입니다. (phase level에 대한 정보는 Identifiers, Binding, and Scopes를 참고하세요.)

trans-expr의 평가는 current-namespacelet-syntax 폼을 확장하는 데 쓰이는 네임스페이스와 바인딩·변수를 공유하되 기본 phase가 하나 더 큰 네임스페이스로 설정하도록 파라미터라이즈됩니다.

idbody에 바인딩되며, 다른 trans-expr에는 바인딩되지 않습니다.

letrec-syntax

syntax

(letrec-syntax ([id trans-expr] ...) body ...+)

splicing-letrec-syntax도 함께 보세요.

id가 모든 trans-expr 안에도 바인딩된다는 점을 제외하면 let-syntax와 같습니다.

let-syntaxes

syntax

(let-syntaxes ([(id ...) trans-expr] ...) body ...+)

splicing-let-syntaxes도 함께 보세요.

let-syntax와 같지만, 각 trans-expr가 해당 id의 개수만큼의 값을 만들어내야 하고, 각각이 해당 값에 바인딩됩니다.

letrec-syntaxes

syntax

(letrec-syntaxes ([(id ...) trans-expr] ...) body ...+)

splicing-letrec-syntaxes도 함께 보세요.

id가 모든 trans-expr 안에도 바인딩된다는 점을 제외하면 let-syntax와 같습니다.

letrec-syntaxes+values

syntax

(letrec-syntaxes+values ([(trans-id ...) trans-expr] ...)
                        ([(val-id ...) val-expr] ...)
                        body ...+)

letrec-syntaxesletrec-values의 변형과 결합한 형태입니다. 각 trans-idval-id는 모든 trans-exprval-expr에 바인딩됩니다.

letrec-syntaxes+values 폼은 지역 컴파일-타임 바인딩의 핵심 폼입니다. letrec-syntax 같은 폼과 내부 정의 문맥이 이것으로 확장되기 때문입니다. 완전히 확장된 표현식(Fully Expanded Programs 참고)에서는 trans-id 바인딩이 버려지고, 폼은 letrec-valueslet-values의 결합으로 줄어듭니다.

letrec-syntaxes+values로 바인딩되는 변수의 경우 위치 생성 규칙이 letrec-values와 약간 다릅니다. [(val-id ...) val-expr] 바인딩 절들은 다음 규칙을 만족하는 최소 집합들로 분할됩니다: 어떤 절이 (완전 확장에서) 더 앞선 절의 val-expr가 참조하는 val-id 바인딩을 갖는다면, 그 두 절과 그 사이의 모든 절은 같은 집합에 속합니다. 집합이 단일 절로 구성되고 그 절의 val-expr가 그 절의 어떤 val-id도 참조하지 않는다면, val-id의 위치는 val-expr가 평가된 뒤에 생성됩니다. 그 외의 경우, 집합의 모든 val-id의 위치는 집합의 첫 val-expr가 평가되기 직전에 생성됩니다. 집합을 형성하는 목적을 위해, (quote-syntax datum #:local) 폼은 letrec-syntaxes+values 폼의 모든 바인딩에 대한 참조로 간주됩니다.

위치 생성 규칙의 최종 결과는 스코프와 평가 순서가 letrec-values와 같지만, 컴파일러가 위치 생성을 최적화·제거할 자유가 더 많다는 것입니다. 이 규칙은 let-valuesletrec-values의 중첩에도 대응하는데, 이것이 완전히 확장된 표현식에서 letrec-syntaxes+values가 나타나는 방식입니다.

local도 함께 보세요. localdefine, define-syntax 등을 포함한 지역 바인딩을 지원합니다.

더 알아보기