지역 바인딩: 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-id는 body 안에서 그 프로시저 자신에 바인딩됩니다.
예시:
> (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-expr와 body에 바인딩되며, 각 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-expr와 body에 바인딩됩니다.
예시:
> (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도 함께 보세요.
각 id를 trans-expr의 값으로 transformer 바인딩(Transformer Bindings 참고)을 만듭니다. trans-expr는 주변 문맥에 대해 phase level 1에서의 표현식입니다. (phase level에 대한 정보는 Identifiers, Binding, and Scopes를 참고하세요.)
각 trans-expr의 평가는 current-namespace를 let-syntax 폼을 확장하는 데 쓰이는 네임스페이스와 바인딩·변수를 공유하되 기본 phase가 하나 더 큰 네임스페이스로 설정하도록 파라미터라이즈됩니다.
각 id는 body에 바인딩되며, 다른 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-syntaxes를 letrec-values의 변형과 결합한 형태입니다. 각 trans-id와 val-id는 모든 trans-expr와 val-expr에 바인딩됩니다.
letrec-syntaxes+values 폼은 지역 컴파일-타임 바인딩의 핵심 폼입니다. letrec-syntax 같은 폼과 내부 정의 문맥이 이것으로 확장되기 때문입니다. 완전히 확장된 표현식(Fully Expanded Programs 참고)에서는 trans-id 바인딩이 버려지고, 폼은 letrec-values나 let-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-values와 letrec-values의 중첩에도 대응하는데, 이것이 완전히 확장된 표현식에서 letrec-syntaxes+values가 나타나는 방식입니다.
local도 함께 보세요. local은 define, define-syntax 등을 포함한 지역 바인딩을 지원합니다.