1.2 구문 모델
1.2 구문 모델 (Syntax Model)
Racket 프로그램의 구문은 두 단계로 정의돼요. 먼저 **읽기 단계(read pass)**가 문자 스트림을 구문 객체로 만들고, 이어서 **확장 단계(expand pass)**가 구문 객체를 완전히 파싱된 형태로 만든답니다. 이 장에서는 식별자의 바인딩, 구문 객체, 확장 과정을 저수준에서 설명해요.
출처: Racket Reference
본문
Racket 프로그램의 구문은 다음에 의해 정의됩니다:
- 문자 스트림을 구문 객체로 처리하는 읽기 단계(read pass), 그리고
- 구문 객체를 완전히 파싱된 형태로 만드는 확장 단계(expand pass).
읽기 단계에 대한 자세한 내용은 The Reader를 보세요. 소스 코드는 보통 read-syntax 모드로 읽히는데, 이 모드는 구문 객체를 만들어 냅니다.
확장 단계는 구문 객체를 재귀적으로 처리해 프로그램의 완전한 파싱을 만들어 냅니다. 구문 객체 안의 바인딩 정보가 확장 과정을 이끌며, 확장 과정이 바인딩 형식(form)을 만나면 하위 표현식의 구문 객체에 새 바인딩 정보를 덧붙입니다.
1.2.1 식별자, 바인딩, 스코프 (Identifiers, Binding, and Scopes)
바인딩에 대한 소개는 The Racket Guide의 식별자와 바인딩을 보세요.
식별자(identifier)는 소스 프로그램의 한 존재(实体)입니다. Racket 프로그램을 파싱(즉 확장)하면 일부 식별자는 변수에 대응하고, 일부는 구문 형식(함수의 구문 형식인 lambda 같은 것)을 가리키며, 일부는 매크로 확장용 트랜스포머를 가리키고, 일부는 심볼이나 구문 객체를 만들기 위해 인용됩니다. 전자가 변수 또는 구문 형식으로 파싱되고 후자가 전자에 대한 참조로 파싱될 때, 전자는 후자를 **바인딩(binding)**한다고 하고(즉 바인딩이며), 후자는 **바인딩되었다(bound)**고 합니다.
예를 들어 소스의 한 조각으로서
(let ([x 5]) x)
라는 텍스트는 let과 x(두 번 등장)라는 두 식별자를 포함합니다. 이 소스가 let이 보통 의미를 갖는 문맥에서 파싱되면, 첫 번째 x가 두 번째 x를 바인딩합니다.
바인딩과 참조는 **스코프 집합(scope set)**을 통해 결정됩니다. 스코프(scope)는 소스의 일부이거나 소스의 전개(elaboration)를 통해 합성된 프로그램의 한 영역에 대응합니다. 중첩된 바인딩 문맥(중첩 함수 같은 것)은 중첩된 스코프를 만들고, 매크로 확장은 더 복잡한 방식으로 겹치는 스코프를 만듭니다. 개념적으로 각 스코프는 고유한 토큰으로 표현되지만 그 토큰에 직접 접근할 수는 없습니다. 대신 각 스코프는 프로그램 표현 내부에 있는 값으로 표현됩니다.
**형식(form)**은 식별자나 함수 호출 같은 프로그램의 한 조각입니다. 형식은 구문 객체로 표현되며, 각 구문 객체는 연관된 스코프 집합(즉 scope set)을 갖습니다. 위 예에서 x들의 표현은 let 형식에 해당하는 스코프를 포함합니다.
한 형식이 특정 식별자의 바인딩으로 파싱되면, 파싱은 식별자의 심볼과 스코프 집합의 조합을 그 의미(변수, 구문 형식, 트랜스포머)에 매핑하는 전역 테이블을 갱신합니다. 어떤 식별자가 특정 바인딩을 참조하는 것은, 참조 심볼과 그 식별자의 심볼이 같고, 참조의 스코프 집합이 바인딩의 스코프 집합의 상위 집합(superset)일 때입니다. 주어진 식별자에 대해, 그 식별자의 스코프 집합의 부분 집합인 스코프 집합을 갖는 바인딩이 여럿 있을 수 있습니다. 그 경우 식별자는 다른 모든 것의 상위 집합인 스코프 집합을 가진 바인딩을 참조합니다. 그런 바인딩이 없으면 참조는 **모호(ambiguous)**하며(표현식으로 파싱되면 구문 오류를 유발합니다), 바인딩은 같은 심볼이지만 스코프가 부분 집합인 모든 바인딩을 가립니다(shadow).
예를 들어
(let ([x 5]) x)
에서 let이 보통의 구문 형식에 해당하는 문맥에서는, let의 파싱이 x의 바인딩을 위한 새 스코프를 도입합니다. 두 번째 x는 let 본문의 일부로서 그 스코프를 받으므로 첫 번째 x가 두 번째 x를 바인딩합니다. 더 복잡한 경우
(let ([x 5])
(let ([x 6])
x))
에서 안쪽 let은 두 번째 x를 위한 두 번째 스코프를 만들므로, 그 스코프 집합은 첫 번째 x의 스코프 집합의 상위 집합이 됩니다. 즉 두 번째 x의 바인딩이 첫 번째 x의 바인딩을 가리고, 세 번째 x는 두 번째 것이 만든 바인딩을 참조하는 것입니다.
**최상위 바인딩(top-level binding)**은 최상위에서의 정의에서 온 바인딩이고, **모듈 바인딩(module binding)**은 모듈 안의 정의에서 온 바인딩이며, 그 외의 모든 바인딩은 **지역 바인딩(local binding)**입니다. 모듈 안에서 최상위 바인딩에 대한 참조는 허용되지 않습니다. 바인딩이 없는 식별자는 unbound입니다.
문서 전체에서 식별자는 파싱되는 방식을 암시하도록 타입세트로 표시됩니다. lambda처럼 하이퍼링크된 식별자는 구문 형식이나 변수에 대한 참조를 나타냅니다. x 같은 일반 식별자는 변수이거나 지정되지 않은 최상위 변수에 대한 참조입니다.
모든 바인딩은 참조될 수 있는 **위상 레벨(phase level)**을 가지며, 위상 레벨은 보통 정수에 대응합니다(단, 라벨 위상 레벨(label phase level)은 정수에 대응하지 않습니다). 위상 레벨 0은 둘러싼 모듈의 실행 시간(또는 최상위 표현식의 실행 시간)에 대응하며, 위상 레벨 0의 바인딩들이 **기본 환경(base environment)**을 구성합니다. 위상 레벨 1은 둘러싼 모듈(또는 최상위 표현식)이 확장되는 시간에 대응하며, 위상 레벨 1의 바인딩들이 **트랜스포머 환경(transformer environment)**을 구성합니다. 위상 레벨 -1은 둘러싼 모듈이 위상 레벨 1에서 사용되기 위해 임포트되는 다른 모듈의 실행 시간에 대응하며(임포트하는 모듈 기준), 위상 레벨 -1의 바인딩들이 **템플릿 환경(template environment)**을 구성합니다. 라벨 위상 레벨은 어떤 실행 시간에도 대응하지 않습니다. 이는 실행 의존성을 암시하지 않고 바인딩(예: 문서 안의 식별자에 대한 것)을 추적하는 데 쓰입니다.
식별자는 서로 다른 위상 레벨에서 서로 다른 바인딩을 가질 수 있습니다. 더 정확히 말하면, 한 형식에 연관된 스코프 집합이 위상 레벨마다 다를 수 있습니다. 최상위 또는 모듈 문맥은 모든 위상 레벨에서 구별되는 스코프를 암시하지만, 매크로 확장이나 다른 구문 형식에서 온 스코프는 모든 위상에서 형식의 스코프 집합에 추가됩니다. 각 바인딩과 참조의 문맥이 어느 위상 레벨의 스코프 집합이 관련되는지 결정합니다.
**바인딩 공간(binding space)**은 공간에 대한 특정 스코프를 가짐으로써 바인딩을 구별하는 관례입니다. 어떤 식별자의 바인딩이 공간의 스코프를 자기 스코프 집합에 포함하면 그 식별자는 "그 공간에 바인딩되었다"고 합니다. 공간의 스코프는 make-interned-syntax-introducer를 사용해 간접적으로 접근합니다. 즉, 공간은 그 공간의 이름으로 interned된 스코프를 가진 바인딩들의 집합일 뿐이며, 기본 바인딩 공간은 interned된 스코프가 없는 것에 대응합니다. require와 provide 형식은 for-space나 only-space-in 같은 하위 형식을 통해 바인딩 공간을 지원합니다. racket 모듈이 제공하는 다른 어떤 형식도 지정된 공간에서 식별자를 바인딩하거나 참조하지 않습니다. 그런 형식은 새 매크로로 구현되도록 의도된 것입니다. 관례상 어떤 식별자가 공간에 바인딩되면, 그에 대응하는 식별자도 기본 바인딩 공간에 바인딩되어야 합니다. 이 관례는 임포트 사이의 불일치나 일부 공간에서만 가리는 지역 바인딩으로 인한 불일치를 피하는 데 도움이 됩니다.
base 패키지 6.3 버전에서 변경: 지역 바인딩이 최상위·모듈 바인딩처럼 특정 위상 레벨을 갖도록 변경했습니다. 8.2.0.3 버전에서 변경: 바인딩 공간을 추가했습니다.
1.2.2 구문 객체 (Syntax Objects)
구문 객체에 대한 소개는 The Racket Guide의 구문 객체를 보세요.
구문 객체는 심볼이나 쌍(pair) 같은 더 단순한 Racket 값을, 어휘 정보, 소스 위치 정보, 구문 속성, 그리고 구문 객체가 tainted인지 여부와 결합합니다. 구문 객체의 어휘 정보는 각 위상 레벨마다 하나씩인 스코프 집합들의 집합으로 이뤄집니다. 특히 식별자는 심볼을 담은 구문 객체로 표현되며, 그 어휘 정보를 전역 바인딩 테이블과 결합해 각 위상 레벨에서의 바인딩(있다면)을 결정할 수 있습니다.
예를 들어 car 식별자는 그것이 racket/base 언어에서 온 car(즉 내장 car)임을 지정하는 어휘 정보를 가질 수 있습니다. 비슷하게 lambda 식별자의 어휘 정보는 그것이 프로시저 형식을 나타냄을 알려줄 수 있습니다. 어떤 다른 식별자의 어휘 정보는 최상위 변수를 참조함을 알려줄 수 있습니다.
구문 객체가 식별자나 단순 상수보다 복잡한 표현식을 나타낼 때, 그 내부 구성 요소를 추출할 수 있습니다. 추출된 식별자에서도 바인딩에 대한 상세 정보는 대부분 간접적으로만 얻을 수 있습니다. 두 식별자가 같은 바인딩을 참조하는지(즉 free-identifier=?), 또는 두 식별자가 같은 스코프 집합을 가져 하나가 바인딩 위치에 있고 다른 하나가 표현식 위치에 있으면 서로를 바인딩할지(즉 bound-identifier=?)를 검사해 비교할 수 있습니다.
예를 들어
(let ([x 5]) (+ x 6))
처럼 쓰인 프로그램이 구문 객체로 표현될 때, 두 x를 위해 두 개의 구문 객체를 추출할 수 있습니다. free-identifier=?와 bound-identifier=? 술어 모두 그 x들이 같다고 알려줍니다. 대조적으로 let 식별자는 어느 x와도 free-identifier=?도 bound-identifier=?도 아닙니다.
구문 객체 안의 어휘 정보는 구문 객체의 나머지와 독립적이며, 임의의 다른 Racket 값과 결합해 새 구문 객체로 복사될 수 있습니다. 따라서 구문 객체 안의 식별자 바인딩 정보는 식별자의 기호 이름과 식별자의 어휘 정보 양쪽에 근거합니다. 같은 어휘 정보이지만 다른 기본 값을 가진 같은 질문은 다른 답을 낼 수 있습니다.
예를 들어 위 프로그램의 let에서 온 어휘 정보를 'x와 결합해도 두 x 중 하나와 free-identifier=?인 식별자가 되지는 않습니다. 왜냐하면 그것은 x 바인딩의 스코프 안에 나타나지 않기 때문입니다. 대조적으로 6의 어휘 문맥을 'x와 결합하면 두 x 모두와 bound-identifier=?인 식별자가 됩니다.
quote-syntax 형식은 프로그램의 평가와 프로그램의 표현 사이를 잇습니다. 구체적으로 (quote-syntax datum #:local)은 datum이 quote-syntax 형식의 일부로서 파싱되었을 때 가졌던 모든 어휘 정보를 보존하는 구문 객체를 만들어 냅니다. (quote-syntax datum) 형식은 비슷하지만 datum의 스코프 집합에서 특정 스코프를 제거한다는 점에 주의하세요. 자세한 내용은 quote-syntax를 보세요.
1.2.3 확장(파싱) (Expansion (Parsing))
확장은 위상 레벨 0에서 시작해 특정 위상 레벨의 구문 객체를 재귀적으로 처리합니다. 구문 객체의 어휘 정보로부터 온 바인딩이 확장 과정을 이끌고, 하위 표현식의 어휘 정보에 새 바인딩이 도입되게 합니다. 어떤 경우에는 하위 표현식이 둘러싼 표현식보다 더 깊은 위상(더 큰 위상 레벨 번호)에서 확장됩니다.
1.2.3.1 완전히 확장된 프로그램 (Fully Expanded Programs)
완전한 확장은 다음 문법과 일치하는 구문 객체를 만들어 냅니다:
top-level-form = general-top-level-form
| (#%expression expr)
|
(module id module-path
(#%plain-module-begin
module-level-form ...))
| (begin top-level-form ...)
| (begin-for-syntax top-level-form ...)
module-level-form = general-top-level-form
| (#%provide raw-provide-spec ...)
| (begin-for-syntax module-level-form ...)
| submodule-form
| (#%declare declaration-keyword ...)
submodule-form =
(module id module-path
(#%plain-module-begin
module-level-form ...))
|
(module* id module-path
(#%plain-module-begin
module-level-form ...))
|
(module* id #f
(#%plain-module-begin
module-level-form ...))
general-top-level-form = expr
| (define-values (id ...) expr)
| (define-syntaxes (id ...) expr)
| (#%require raw-require-spec ...)
expr = id
| (#%plain-lambda formals expr ...+)
| (case-lambda (formals expr ...+) ...)
| (if expr expr expr)
| (begin expr ...+)
| (begin0 expr expr ...)
|
(let-values ([(id ...) expr] ...)
expr ...+)
|
(letrec-values ([(id ...) expr] ...)
expr ...+)
| (set! id expr)
| (quote datum)
| (quote-syntax datum)
| (quote-syntax datum #:local)
| (with-continuation-mark expr expr expr)
| (#%plain-app expr ...+)
| (#%top . id)
| (#%variable-reference id)
| (#%variable-reference (#%top . id))
| (#%variable-reference)
| (#%foreign-inline datum keyword)
formals = (id ...)
| (id ...+ . id)
| id
완전히 확장된 구문 객체는 프로그램의 파싱(즉 파싱된 프로그램)에 대응하며, 식별자 위의 어휘 정보가 그 파싱을 가리킵니다.
주의: 완전히 확장된 프로그램에서 식별자의 **기호 이름(symbolic name)**은 위 문법의 기호 이름과 일치하지 않을 수 있습니다. 오직 바인딩만(즉 free-identifier=?에 따르면)이 중요합니다.
더 구체적으로, 위 문법에서 식별자의 타입세트가 중요합니다. 예를 들어 expr의 두 번째 경우는 첫 요소가 식별자인 구문 객체 목록인데, 그 식별자의 어휘 정보는 racket/base 언어의 #%plain-lambda에 대한 바인딩을 지정합니다(즉 그 식별자는 바인딩이 #%plain-lambda인 것과 free-identifier=?입니다). 모든 경우에서 위에서 구문 형식 이름으로 타입세트된 식별자는 Syntactic Forms에 정의된 바인딩을 참조합니다.
기본 위상이 0인 네임스페이스의 완전히 확장된 프로그램에서, 프로그램 안 바인딩의 관련 위상 레벨은, 그 바인딩을 둘러싸는 begin-for-syntax 및/또는 define-syntaxes 형식이 N개 있으면 N입니다. 단, module 또는 module* 형식의 본문을 감싸는 begin-for-syntax 형식은 module* 형식이 id 뒤 module-path 자리에 #f를 갖지 않는 한 세지 않습니다. quote-syntax 형식 안의 datum은 모든 위상 레벨에 대한 정보를 보존합니다.
완전히 확장된 프로그램에서 지역 바인딩에 대한 참조는 그 바인딩 식별자와 정확히 일치하는 스코프 집합을 가집니다. 추가 스코프는 있다면 제거됩니다. 그 결과 bound-identifier=?로 지역 바인딩 식별자와 참조 식별자를 연관지을 수 있고, free-identifier=?로 참조를 모듈 바인딩이나 최상위 바인딩과 관련지어야 합니다.
위 문법에 추가로, #%expression은 완전히 지역 확장된 표현식 위치에 나타날 수 있습니다. 예를 들어 멈춤 목록(stop list)이 비었을 때 local-expand의 결과에 #%expression이 나타날 수 있습니다. 참조 식별자의 스코프 집합은 local-expand의 멈춤 목록이 비어 있을 때만 지역 확장된 표현식에서 축소됩니다.
base 패키지 6.3 버전에서 변경: quote-syntax의 #:local 변형을 추가했습니다. 완전히 지역 확장된 형식에 나타날 수 있는 것에서 letrec-syntaxes+values를 제거했습니다.
1.2.3.2 확장 단계 (Expansion Steps)
재귀 확장에서, 특정 위상 레벨의 구문 객체를 확장하는 각 단일 단계는 확장되는 구문 객체의 즉각적인 모양에 달려 있습니다:
-
식별자(즉 구문-객체 심볼)인 경우: 식별자의 어휘 정보로 바인딩이 결정됩니다. 식별자에 바인딩이 있으면 그 바인딩으로 계속 진행합니다. 식별자가 unbound이면, implicit-made-explicit 속성을 가진 식별자의 어휘 정보를 사용해 새 구문-객체 심볼
'#%top을 만듭니다. 이#%top식별자에 바인딩이 없으면 파싱이exn:fail:syntax예외로 실패합니다. 그렇지 않으면 원래 식별자와 같은 어휘 정보를 사용해 새 식별자를 원래 식별자와 함께 새 구문-객체 쌍으로 결합하고,#%top바인딩으로 계속 진행합니다.base패키지 6.3 버전에서 변경: 최상위 문맥에서#%top도입이 unbound 식별자에 대해서만 일어나도록 변경했습니다. -
첫 요소가 식별자인 구문-객체 쌍이고 그 식별자가 최상위 변수 이외의 바인딩을 가진 경우: 그 식별자의 바인딩으로 계속 진행합니다.
-
그 밖의 어떤 형태의 구문-객체 쌍인 경우: implicit-made-explicit 속성을 가진 쌍의 어휘 정보를 사용해 새 구문-객체 심볼
'#%app을 만듭니다. 결과#%app식별자에 바인딩이 없으면 파싱이exn:fail:syntax예외로 실패합니다. 그렇지 않으면 새 식별자를 원래 쌍과 같은 어휘 정보를 사용해 원래 쌍과 결합해 새 구문-객체 쌍을 만들고,#%app바인딩으로 계속 진행합니다. -
그 밖의 어떤 구문 객체인 경우: implicit-made-explicit 속성을 가진 원래 구문 객체의 어휘 정보를 사용해 새 구문-객체 심볼
'#%datum을 만듭니다. 결과#%datum식별자에 바인딩이 없으면 파싱이exn:fail:syntax예외로 실패합니다. 그렇지 않으면 새 식별자를 원래 구문 객체와 같은 어휘 정보를 사용해 원래 구문 객체와 새 구문-객체 쌍으로 결합하고,#%datum바인딩으로 계속 진행합니다.
따라서 실패하지 않는 경우는 특정 바인딩을 가진 식별자로 이어집니다. 이 바인딩은 세 가지 중 하나를 가리킵니다:
-
트랜스포머(transformer):
define-syntax나let-syntax로 도입된 것 같은 것입니다. 연관 값이 인자 하나짜리 프로시저이면 그 프로시저가 구문 트랜스포머로 호출되고(아래에서 설명), 파싱이 그 구문-객체 결과로 다시 시작합니다. 트랜스포머 바인딩이 다른 어떤 종류의 값이라면 파싱이exn:fail:syntax예외로 실패합니다. 구문 트랜스포머에 대한 호출은current-namespace를, 확장에 쓰이는 네임스페이스와 바인딩 및 변수를 공유하지만 기본 위상이 하나 더 큰 네임스페이스로 설정하도록 파라미터화됩니다. -
변수 바인딩: 모듈 수준
define이나let으로 도입된 것 같은 것입니다. 이 경우 파싱되는 형식이 그냥 식별자이면 대응 변수에 대한 참조로 파싱됩니다. 파싱되는 형식이 구문-객체 쌍이면, 구문-객체 쌍의 첫 항목이 식별자가 아닐 때와 같은 방식으로(위 열거의 세 번째 경우)#%app을 구문-객체 쌍의 앞에 추가하고 파싱을 계속합니다. -
핵심 구문 형식(core syntactic form, 흔히 core form으로 줄임): Syntactic Forms에서 각 형식에 대해 설명된 대로 파싱됩니다. 핵심 구문 형식을 파싱하는 것은 보통 하위 형식의 재귀 파싱을 포함하며, 하위 형식의 파싱을 결정하는 바인딩을 도입할 수 있습니다.
확장기가 #%top, #%app, #%datum 식별자를 추가할 때, 그것에 implicit-made-explicit 속성을 부여합니다. 즉 값이 #t인 'implicit-made-explicit 구문 속성, 그리고 그 식별자에 어휘 정보를 주는 구문 객체가 그 속성을 갖고 있다면 그 암시적 식별자가 syntax-original?의 의미에서 original임을 나타내는 숨겨진 속성입니다.
base 패키지 7.9.0.13 버전에서 변경: implicit-made-explicit 속성을 추가했습니다.
1.2.3.3 확장 문맥 (Expansion Context)
각 확장 단계는 특정 문맥에서 일어나며, 트랜스포머와 핵심 구문 형식은 문맥에 따라 다르게 확장될 수 있습니다. 예를 들어 module 형식은 최상위 문맥이나 모듈 문맥에서만 허용되고, 다른 문맥에서는 실패합니다. 가능한 문맥은 다음과 같습니다:
- 최상위 문맥(top-level context): 어떤 모듈·정의·표현식 밖에 있습니다. 단, 최상위
begin형식의 하위 표현식도 최상위 형식으로 확장됩니다. - module-begin 문맥(module-begin context): 모듈 본문 안에 있으며, 모듈 안의 유일한 형식으로 있습니다.
- 모듈 문맥(module context): 모듈 본문 안에 있습니다(module-begin 층 안쪽).
- 내부-정의 문맥(internal-definition context): 정의와 표현식을 모두 허용하는 중첩 문맥입니다.
- 표현식 문맥(expression context): 표현식만 허용되는 문맥입니다.
서로 다른 핵심 구문 형식은 서로 다른 문맥을 사용해 하위 형식을 파싱합니다. 예를 들어 let 형식은 항상 바인딩의 오른쪽 표현식을 표현식 문맥에서 파싱하지만, 본문 파싱은 내부-정의 문맥에서 시작합니다.
1.2.3.4 바인딩 도입 (Introducing Bindings)
확장 중 특정 핵심 구문 형식을 만날 때 바인딩이 도입됩니다:
-
최상위나 모듈 수준에서
require형식을 만나면, 형식이 지정하는 각 심볼이 그 명세의 스코프 집합과 짝지어져 새 바인딩을 도입합니다.require형식에서 달리 지정하지 않으면 바인딩은 내보내는 모듈이 지정한 위상 레벨, 즉 각 일반provide에 대해 위상 레벨 0, 각for-syntaxprovide에 대해 위상 레벨 1 등으로 도입됩니다.for-metaprovide 형식은 임의의 위상 레벨로 내보내기(그 위상 레벨에 바인딩이 모듈 안에 존재하기만 하면)를 허용합니다.require안의for-syntax하위 형식도 비슷하게 임포트하지만, 결과 바인딩은 내보내는 위상 레벨보다 하나 더 큰 위상 레벨을 가지며, 라벨 위상 레벨로의 내보내기는 여전히 라벨 위상 레벨로 임포트됩니다. 더 일반적으로require안의for-meta하위 형식은 지정된 위상 레벨 이동으로 임포트하며, 지정된 이동이#f이거나for-label로 임포트하면 모든 바인딩이 라벨 위상 레벨로 임포트됩니다. -
최상위나 모듈 수준에서
define,define-values,define-syntax,define-syntaxes형식을 만나면, 정의되는 각 식별자에 대해 위상 레벨 0(즉 기본 환경이 확장됨)에 바인딩이 추가됩니다. -
최상위나 모듈 수준에서
begin-for-syntax형식을 만나면,define-values와define-syntaxes에서처럼 바인딩이 도입되지만 위상 레벨 1(즉 트랜스포머 환경이 확장됨)에서 도입됩니다. 더 일반적으로begin-for-syntax형식은 중첩될 수 있고, 각begin-for-syntax는 그 본문을 한 위상 레벨씩 이동시킵니다. -
let-values형식을 만나면,let-values형식의 본문이 새 스코프로 확장됩니다(새 구문 객체를 만들어). 그 스코프는 식별자 자체에 추가되어, 바인딩 위치의 식별자가 완전히 확장된 형식의 사용과bound-identifier=?가 되고 다른 식별자와는bound-identifier=?가 아니게 합니다. 새 바인딩은let-values형식이 확장되는 위상 레벨에 있습니다. -
letrec-values또는letrec-syntaxes+values형식을 만나면, 오른쪽 표현식도 새 스코프로 확장된다는 점만 빼고let-values에서처럼 바인딩이 추가됩니다. -
내부-정의 문맥의 정의는 Internal Definitions에 설명된 대로 새 스코프와 바인딩을 도입합니다.
예를 들어
(let-values ([(x) 10]) (+ x y))
에서 x를 위해 도입된 바인딩은 본문의 x에 적용됩니다. 왜냐하면 새 스코프가 만들어져 바인딩 x와 참조 x 양쪽에 추가되기 때문입니다. 같은 스코프가 y에도 추가되지만, 바인딩 x와 다른 심볼을 가지므로 새 바인딩을 참조하지 않습니다. 이 let-values 형식 밖의 어떤 x도 새 스코프를 받지 않으므로 새 바인딩을 참조하지 않습니다.
1.2.3.5 트랜스포머 바인딩 (Transformer Bindings)
최상위 문맥이나 모듈 문맥에서 확장기가 define-syntaxes 형식을 만나면, 그 정의된 식별자를 위해 도입하는 바인딩은 **트랜스포머 바인딩(transformer binding)**입니다. 바인딩의 값은 실행 시간이 아니라 확장 시간에 존재하지만(두 시간이 겹칠 수는 있습니다), 바인딩 자체는 위상 레벨 0(즉 기본 환경)으로 도입됩니다.
바인딩의 값은 define-syntaxes 형식의 표현식을 평가해 얻습니다. 이 표현식은 평가되기 전에 확장(즉 파싱)되어야 하며, 위상 레벨 0이 아니라 위상 레벨 1(즉 트랜스포머 환경)에서 확장됩니다.
결과 값이 인자 하나짜리 프로시저이거나 make-set!-transformer를 프로시저에 적용한 결과이면, 그것은 구문 트랜스포머(일명 매크로)로 사용됩니다. 그 프로시저는 구문 객체를 받아 구문 객체를 반환할 것으로 기대됩니다. (위상 레벨 0에서의) 바인딩 사용은 확장기가 구문 트랜스포머를 호출하게 합니다. 자세한 내용은 Expansion Steps를 보세요.
확장기가 트랜스포머에 구문 객체를 넘기기 전에, 그 구문 객체는 매크로 사용 지점의 구문 객체와 매크로가 도입하는 구문 객체를 구별하기 위해 (모든 하위 구문 객체에 적용되는) 새 **매크로-도입 스코프(macro-introduction scope)**로 확장됩니다. 트랜스포머의 결과에서는 그 스코프의 존재가 뒤집혀서, 도입된 구문 객체는 스코프를 유지하고 사용 지점의 구문 객체는 그것을 갖지 않게 됩니다. 추가로, 트랜스포머의 사용이 그 바인딩과 같은 정의 문맥에 있으면, 사용 지점의 구문 객체는 트랜스포머 결과에서 뒤집히지 않는 추가 새 **사용-지점 스코프(use-site scope)**로 확장되어, 사용 지점의 구문 객체만 사용-지점 스코프를 갖게 됩니다.
매크로 확장의 스코프 도입 과정은 확장된 프로그램의 바인딩을 소스 프로그램의 어휘 구조와 일관되게 유지하는 데 도움이 됩니다. 예를 들어 프로그램
(define x 12)
(define-syntax m
(syntax-rules ()
[(_ id) (let ([x 10]) id)]))
(m x)
의 확장된 형태는
(define x 12)
(define-syntax m ....)
(let ([x 10]) x)
입니다. 그러나 마지막 표현식의 결과는 10이 아니라 12입니다. 그 이유는 m에 바인딩된 트랜스포머가 바인딩 x를 도입하지만, 참조하는 x는 트랜스포머의 인자에 존재하기 때문입니다. 도입된 x에는 하나의 새 스코프가 남고 참조 x에는 다른 새 스코프가 있으므로, 바인딩 x는 본문 x와 bound-identifier=?가 아닙니다.
바인딩 식별자 위의 사용-지점 스코프는, 그 정의가 사용-지점 스코프가 도입된 문맥과 같은 문맥에 있을 때 무시됩니다. 이 사용-지점 스코프의 특별한 처리는 매크로가 보이는 정의로 확장되게 합니다. 예를 들어 프로그램
(define-syntax m
(syntax-rules ()
[(_ id) (define id 5)]))
(m x)
x
의 확장된 형태는
(define-syntax m ....)
(define x 5)
x
인데, define 형식의 x에 마지막 x에는 없는 사용-지점 스코프가 있습니다. 하지만 마지막 x는 여전히 그 정의를 참조합니다. 왜냐하면 정의의 바인딩을 설치하기 전에 사용-지점 스코프가 효과적으로 제거되기 때문입니다. 대조적으로
(define-syntax m
(syntax-rules ()
[(_ id) (let ([x 4])
(let ([id 5])
x))]))
(m x)
의 확장은
(define-syntax m ....)
(let ([x 4])
(let ([x 5])
x))
인데, 두 번째 x에 마지막 x를 바인딩하지 못하게 하는 사용-지점 스코프가 있습니다. 이 경우 사용-지점 스코프는 무시되지 않습니다. 왜냐하면 그 바인딩이 (m x)가 확장된 정의 문맥의 일부가 아니기 때문입니다.
set! 형식은 make-set!-transformer와 prop:set!-transformer 속성과 함께 동작해, set! 표현식을 변형하는 **할당 트랜스포머(assignment transformer)**를 지원합니다. 할당 트랜스포머는 확장기가 보통 트랜스포머를 적용하는 것과 같은 방식으로 set!이 적용하는 프로시저를 담고 있습니다.
make-rename-transformer 프로시저나 prop:rename-transformer 속성은, 트랜스포머 바인딩의 값으로서 확장기와 set!이 특별히 처리하는 값도 만듭니다. id가 make-rename-transformer가 만든 리네임 트랜스포머에 바인딩되어 있으면, make-rename-transformer에 전달된 대상 식별자로 대체됩니다. 추가로 대상 식별자의 'not-free-identifier=? 구문 속성 값이 참이 아닌 한, 바인딩 테이블이 id가 그 리네임 트랜스포머의 식별자의 별칭임을 나타내도록 확장됩니다. free-identifier=? 함수는 바인딩의 동일성을 결정하기 위해 별칭 체인을 따라가고, identifier-binding 함수도 비슷하게 별칭 체인을 따라가며, provide 형식은 id를 대상 식별자로 내보냅니다. 마지막으로 syntax-local-value 함수는 바인딩 별칭이 설치되지 않았어도 리네임 트랜스포머 체인을 따라갑니다.
식별자를 추적하기 위해 스코프를 사용하는 것 외에도, 확장기는 'origin 같은 구문 속성을 통해 형식의 확장 이력을 추적합니다. 자세한 내용은 Syntax Object Properties를 보세요.
letrec-syntaxes+values에 대한 확장기의 처리는 define-syntaxes에 대한 처리와 비슷합니다. letrec-syntaxes+values는 임의의 위상 레벨 n(0뿐 아니라)에서 확장될 수 있으며, 그 경우 트랜스포머 바인딩의 표현식은 위상 레벨 n+1에서 확장됩니다.
begin-for-syntax 형식의 표현식은 define-syntaxes와 같은 방식으로 확장되고 평가됩니다. 그러나 begin-for-syntax 안의 정의로부터 도입된 바인딩은 (위상 레벨 0의 트랜스포머 바인딩이 아니라) 위상 레벨 1에 있습니다.
1.2.3.6 지역 바인딩 문맥 (Local Binding Context)
식별자의 바인딩은 어휘 정보와 전역 바인딩 테이블의 결합에서 유일하게 결정될 수 있지만, 확장기는 또한 지역 바인딩에 대한 추가 정보를 기록하는 **지역 바인딩 문맥(local binding context)**을 유지해, 그것이 바인딩된 어휘 영역 밖에서 사용되지 않게 합니다.
let 같은 지역 바인딩 형식이 바인딩된 식별자와 본문 형식 양쪽에 새 스코프를 추가하는 방식 때문에, 식별자가 let의 본문에 나타나지 않고서 지역 바인딩을 참조하는 것은 보통 불가능합니다. 그러나 매크로가 컴파일-시간 상태를 사용해 바인딩된 식별자를 숨겨두거나, local-expand를 사용해 확장된 바인딩 형식에서 식별자를 추출하면 이 제약을 위반할 수 있습니다. 예를 들어 다음 stash-id와 unstash-id 매크로는 협력해 지역적으로 바인딩된 x 식별자에 대한 참조를 그것이 바인딩된 어휘 영역 밖으로 옮깁니다:
> (begin-for-syntax
(define stashed-id #f))
> (define-syntax (stash-id stx)
(syntax-case stx ()
[(_ id)
(begin
(set! stashed-id #'id)
#'(void))]))
> (define-syntax (unstash-id stx)
stashed-id)
> (let ([x 42])
(stash-id x)
(unstash-id))
42
> (unstash-id)
eval:5:0: x: identifier used out of context
in: x
일반적으로 식별자의 어휘 정보는 그 바인딩이 둘러싼 문맥에서 이용 가능한지 여부를 알기에 충분하지 않습니다. 왜냐하면 stashed-id에 저장된 식별자의 스코프 집합이 전역 바인딩 테이블의 바인딩을 모호하지 않게 가리키기 때문입니다. identifier-binding이 #f가 아니라 'lexical을 만든다는 사실에서 이를 관찰할 수 있습니다:
> (define-syntax (stashed-id-binding stx)
#`'#,(identifier-binding stashed-id))
> (stashed-id-binding)
'lexical
그러나 위 프로그램에서 (unstash-id)가 만든 참조는 기술적으로 unbound가 아니어도 여전히 불법입니다. x의 바인딩이 대응하는 let 형식의 본문 안에서만 스코프 안에 있다는 사실을 기록하기 위해, 확장기는 let 본문을 확장하는 동안 x의 바인딩을 지역 바인딩 문맥에 추가합니다. 더 일반적으로 확장기는 그 변수에 대한 참조가 합법적인 표현식을 확장하는 동안 모든 지역 변수 바인딩을 지역 바인딩 문맥에 추가합니다. 확장기가 지역 변수에 바인딩된 식별자를 만나는데 연관 바인딩이 현재 지역 바인딩 문맥에 없으면, 구문 오류를 일으킵니다.
지역 바인딩 문맥은 또한 let-syntax 같은 형식이 바인딩하는 지역 트랜스포머 바인딩을 비슷한 방식으로 추적합니다. 단, 문맥은 트랜스포머와 연관된 컴파일-시간 값도 저장합니다. 지역적으로 트랜스포머로 바인딩된 식별자가 구문 트랜스포머로서 적용 위치에서 사용되거나, 그 컴파일-시간 값이 syntax-local-value로 조회되면, 값을 얻기 위해 지역 바인딩 문맥을 참조합니다. 바인딩이 스코프 안에 있으면 연관 컴파일-시간 값이 사용되고, 그렇지 않으면 확장기가 구문 오류를 일으킵니다.
예:
> (define-syntax (stashed-id-local-value stx)
#`'#,(syntax-local-value stashed-id))
> (let-syntax ([y 42])
(stash-id y)
(stashed-id-local-value))
42
> (stashed-id-local-value)
syntax-local-value: identifier is not bound to syntax:
#<syntax:eval:11:0 y>
1.2.3.7 부분 확장 (Partial Expansion)
특정 문맥(내부-정의 문맥이나 모듈 문맥 같은)에서, 형식이 정의·표현식·기타 선언 형식 중 어느 것을 나타내는지 결정하기 위해 **부분 확장(partial expansion)**이 사용됩니다. 부분 확장은 관련 바인딩이 원시 구문 형식에 대한 것일 때 보통의 재귀 확장을 끊음으로써 동작합니다.
특수한 경우로, 확장이 표현식에 #%app, #%datum, #%top 식별자를 추가하려 하다가 그 바인딩이 원시 #%app, #%datum, #%top 형식임이 드러나면, 확장은 식별자를 추가하지 않고 멈춥니다.
1.2.3.8 내부 정의 (Internal Definitions)
**내부-정의 문맥(internal-definition context)**은 표현식과 섞인 지역 정의를 지원합니다. 내부 정의를 허용하는 형식은 body 메타변수를 사용해 그런 위치를 문서화합니다. 내부-정의 문맥의 정의는 letrec-syntaxes+values를 통한 지역 바인딩과 동등합니다. 매크로 확장은 내부 정의를 letrec-syntaxes+values 형식으로 변환합니다.
확장은 내부-정의 수열의 각 본문의 부분 확장에 의존합니다. 각 본문의 부분 확장은 다음 경우 중 하나와 일치하는 형식을 만듭니다:
define-values형식: 바인딩 테이블이 즉시define-values형식의 바인딩으로 채워집니다. 정의의 추가 확장은 미루어지고, 부분 확장은 본문의 나머지로 계속됩니다.define-syntaxes형식: 오른쪽이 확장되고 평가되며(letrec-syntaxes+values형식에서처럼), 본문 수열에 트랜스포머 바인딩이 설치된 뒤 부분 확장이 본문의 나머지로 계속됩니다.begin이 아닌 원시 표현식 형식: 표현식의 추가 확장은 미루어지고, 부분 확장은 본문의 나머지로 계속됩니다.begin형식:begin의 하위 형식이 내부-정의 수열에 이어 붙여지고, 부분 확장은 새로 이어 붙여진 형식의 첫 번째(또는begin에 하위 형식이 없으면 다음 형식)로 계속됩니다.
모든 본문 형식이 부분 확장된 뒤, 만난 정의가 없으면 표현식들은 내부-정의 문맥의 확장으로서 하나의 begin 형식으로 모입니다. 그렇지 않으면 마지막 정의 뒤에 적어도 하나의 표현식이 나타나야 하며, 정의 사이에 나타나는 모든 expr은 (define-values () (begin expr (values)))로 변환됩니다. 그런 다음 정의들은 letrec-syntaxes+values 형식의 바인딩으로 변환되고, 마지막 정의 뒤의 모든 표현식이 letrec-syntaxes+values 형식의 본문이 됩니다.
부분 확장이 시작되기 전에, 내부-정의 문맥의 확장은 내부-정의 문맥의 내용에 새 **바깥-가장자리 스코프(outside-edge scope)**를 도입하는 것으로 시작합니다. 이 바깥-가장자리 스코프는 원래 형식에 존재하는 구문 객체를 효과적으로 식별합니다. **안쪽-가장자리 스코프(inside-edge scope)**도 만들어져 원래 내용에 추가되며, 또한 어떤 부분 확장의 결과에도 추가됩니다. 이 안쪽-가장자리 스코프는 내부-정의 문맥이 도입하는 모든 바인딩이 공통의 특정 스코프를 갖도록 보장합니다.
1.2.3.9 모듈 확장, 위상, 방문 (Module Expansion, Phases, and Visits)
module 형식의 확장은 내부-정의 문맥의 확장과 비슷하게 진행됩니다. 즉 원래 모듈 내용을 위해 바깥-가장자리 스코프가 만들어지고, 정의와 임포트를 드러내기 위한 모듈 최상위 형식의 부분 확장 중 나타나는 원래 모듈과 모든 형식에 안쪽-가장자리 스코프가 추가됩니다.
require 형식은 확장 시간에 바인딩을 도입할 뿐 아니라, 확장기가 그 형식을 만나면 참조하는 모듈을 **방문(visit)**합니다. 즉 확장기는 begin-for-syntax 안에 정의된 변수를 인스턴스화하고, define-syntaxes 트랜스포머 바인딩을 위한 모든 표현식도 평가합니다.
모듈 방문은 모듈 인스턴스화와 같은 방식으로 require를 통해 전파됩니다. 게다가 모듈이 위상 0에서 방문되면, 그 모듈이 for-syntax로 require하는 어떤 모듈도 위상 1에서 인스턴스화되며, 위상 0으로 돌아오는 추가 for-template require는 해당 모듈이(인스턴스화되지 않고) 위상 0에서 방문되게 합니다.
컴파일 중에 모듈 문맥의 최상위는 그 자체로 암시적으로 방문됩니다. 따라서 확장기가 (require (for-syntax ....))를 만나면, 위상 레벨 1(즉 트랜스포머 환경)에 바인딩을 추가하는 것에 더해 그 required 모듈을 위상 1에서 즉시 인스턴스화합니다. 비슷하게 확장기는 begin-for-syntax 안에서 만나는 어떤 형식도 즉시 평가합니다.
0보다 큰 위상은 요청 시 방문됩니다. 예를 들어 위상-0 let-syntax의 오른쪽이 확장되어야 하면, 위상 1에서 이용 가능한 모듈들이 방문됩니다. 더 일반적으로 위상 n에서 확장을 시작하면 위상 n의 모듈을 방문하고, 이는 차례로 위상 n+1의 모듈을 인스턴스화합니다. 이러한 방문과 인스턴스화는 둘러싼 네임스페이스의 모듈 레지스트리에 있는 이용 가능한 모듈들에 적용됩니다. 레지스트리별 잠금(per-registry lock)이 여러 스레드가 이용 가능한 모듈을 동시에 인스턴스화하고 방문하지 못하게 합니다. 이용 가능한 모듈의 요청 시 인스턴스화는 namespace-call-with-registry-lock과 같은 재진입 잠금을 사용합니다.
확장기가 모듈 문맥 안에서 require와 (require (for-syntax ....))를 만나면, 결과 방문과 인스턴스화는 둘러싼 모듈의 확장에 특정된 것이며, 최상위 문맥이나 다른 모듈의 확장에서 촉발된 방문·인스턴스화와 분리되어 유지됩니다. 같은 맥락에서, 모듈이 namespace-attach-module을 통해 네임스페이스에 붙으면, 그것이 require하는 모듈은 전이적으로 붙지만, 인스턴스들은 네임스페이스의 기본 위상 이하의 위상에서만 붙습니다.
모듈이 0이 아닌 위상에서 인스턴스화되면, 모듈 안의 구문 리터럴은 인스턴스화 위상만큼 이동됩니다. 모듈이 for-label으로 임포트되면, 여러 위상에서 온 제공 바인딩이 모두 라벨 위상 레벨로 매핑되며, 그 바인딩을 가진 구문 객체의 추가 위상 이동의 영향을 받지 않습니다. 그러나 구문 객체가 라벨 위상 레벨로 이동되면, 위상 레벨 0의 바인딩만 라벨 위상 레벨의 바인딩이 되며, 추가 위상 이동이 원래 위상 레벨 중 어느 것이 라벨 위상으로 이동되는지 조정할 수 있습니다. syntax-shift-phase-level을 보세요.
1.2.3.10 매크로-도입 바인딩 (Macro-Introduced Bindings)
최상위 정의가 매크로 확장에서 기원한 식별자를 바인딩하면, 그 정의는 확장을 위해 생성된 새 스코프 때문에 같은 확장이 생성한 식별자 사용만 포착합니다.
예:
> (define-syntax def-and-use-of-x
(syntax-rules ()
[(def-and-use-of-x val)
; x below originates from this macro:
(begin (define x val) x)]))
> (define x 1)
> x
1
> (def-and-use-of-x 2)
2
> x
1
> (define-syntax def-and-use
(syntax-rules ()
[(def-and-use x val)
; "x" below was provided by the macro use:
(begin (define x val) x)]))
> (def-and-use x 3)
3
> x
3
모듈 밖의 최상위 정의에서, 평가 순서는 생성된 식별자 사용에 대한 생성된 정의의 바인딩에 영향을 줍니다. 사용이 정의보다 앞서면, 그 사용은 그 시점에 존재하는 바인딩으로 해석되며, 이는 이후 매크로가 생성한 정의의 바인딩을 포함하지 않습니다. (모듈 안에서는 그런 순서 의존이 없습니다. 왜냐하면 모듈 바인딩이 모듈 본문 전체를 덮기 때문입니다.) 사용 전에 식별자 선언을 지원하기 위해, define-syntaxes 형식은 그 define-syntaxes 선언의 본문이 결과를 0개 만들면 식별자를 바인딩하지 않습니다.
예:
> (define bucket-1 0)
> (define bucket-2 0)
> (define-syntax def-and-set!-use-of-x
(syntax-rules ()
[(def-and-set!-use-of-x val)
(begin (set! bucket-1 x) (define x val) (set! bucket-2 x))]))
> (define x 1)
> (def-and-set!-use-of-x 2)
> x
1
> bucket-1
1
> bucket-2
2
> (define-syntax defs-and-uses/fail
(syntax-rules ()
[(def-and-use)
(begin
; Initial reference to even precedes definition:
(define (odd x) (if (zero? x) #f (even (sub1 x))))
(define (even x) (if (zero? x) #t (odd (sub1 x))))
(odd 17))]))
> (defs-and-uses/fail)
even: undefined;
cannot reference an identifier before its definition
in module: top-level
> (define-syntax defs-and-uses
(syntax-rules ()
[(def-and-use)
(begin
; Declare before definition via no-values define-syntaxes:
(define-syntaxes (odd even) (values))
(define (odd x) (if (zero? x) #f (even (sub1 x))))
(define (even x) (if (zero? x) #t (odd (sub1 x))))
(odd 17))]))
> (defs-and-uses)
#t
매크로가 생성한 require와 provide 절도 (추가된 스코프 때문에) 생성별 바인딩을 도입하고 참조하며, 정의와 같은 순서 효과를 가집니다. 바인딩은 형식의 특정 부분에 붙은 스코프 집합에 달려 있습니다:
require에서,(rename-in [orig-id bind-id])나(only-in .... [orig-id bind-id])형태의 require-spec에 대해bind-id가 바인딩의 스코프 집합을 제공합니다. 다른 require-spec에 대한require에서 생성기(generator)가 스코프 집합을 결정합니다.provide에서,id형태의 provide-spec에 대해 내보내는 식별자는id를 바인딩하는 것이지만, 외부 이름은id의 평범한 심볼 부분입니다.all-except-out에 대한 예외도 비슷하게 결정되며,rename-out형식의orig-id바인딩도 그렇고, 외부 이름에는 평범한 심볼이 사용됩니다.all-defined-out의 경우(all-defined-out)형식의 스코프만 가진 정의가 있는 식별자만 내보내집니다. 외부 이름은 정의에서 온 평범한 심볼입니다.
1.2.4 컴파일 (Compilation)
확장된 코드가 평가되기 전에, 먼저 **컴파일(compile)**됩니다. 컴파일된 형식은 본질적으로 대응하는 확장된 형식과 같은 정보를 갖지만, 내부 표현은 당연히 구문 형식과 지역 바인딩을 위한 식별자를 생략합니다. 한 가지 중요한 차이는 컴파일된 형식이 거의 완전히 불투명해서, 그것이 담은 정보에 직접 접근할 수 없다는 점입니다(그래서 일부 식별자가 버려질 수 있습니다). 동시에 컴파일된 형식은 바이트 문자열로 마샬링되거나 그로부터 복원될 수 있어, 코드를 저장하고 다시 불러오는 데 적합합니다.
개별적인 읽기·확장·컴파일·평가 연산을 이용할 수 있지만, 그 연산들은 종종 자동으로 결합됩니다. 예를 들어 eval 프로시저는 구문 객체를 받아 확장하고, 컴파일하고, 평가합니다.
1.2.5 네임스페이스 (Namespaces)
네임스페이스를 조작하는 함수는 Namespaces를 보세요.
네임스페이스(namespace)는 파싱의 시작점이자 컴파일된 코드를 실행하는 시작점입니다. 네임스페이스는 또한 모듈 이름을 모듈 선언에 매핑하는 **모듈 레지스트리(module registry)**를 가집니다(Modules and Module-Level Variables 참조). 이 레지스트리는 모든 위상 레벨이 공유하며, 파싱과 컴파일된 코드 실행 양쪽에 적용됩니다.
파싱의 시작점으로서 네임스페이스는 스코프(위상 레벨마다 하나씩, 그리고 모든 위상 레벨에 걸친 하나)를 제공합니다. namespace-require 같은 연산은 네임스페이스의 스코프를 사용해 초기 바인딩을 만들고, 네임스페이스에서의 추가 확장·평가가 추가 바인딩을 만들 수 있습니다. 네임스페이스로 형식을 평가하면 항상 네임스페이스의 위상별 스코프를 그 형식과 최상위 형식 확장 결과에 추가합니다. 결과적으로 모든 바인딩 식별자에 적어도 하나의 스코프가 있습니다. 네임스페이스의 추가 스코프는 요청 시에만 추가됩니다(예: eval-syntax가 아니라 eval을 사용). 요청되면 추가 스코프가 모든 위상 레벨에 추가됩니다. 모듈이 만든 네임스페이스(module->namespace 참조)를 제외하고, 모든 네임스페이스는 모든 위상 레벨에 추가되는 것과 같은 스코프를 사용하며, 위상 레벨에 특정된 스코프는 항상 구별됩니다.
컴파일된 코드 평가의 시작점으로서 각 네임스페이스는 다양한 위상의 구별되는 최상위 변수 집합과, 각 위상의 잠재적으로 구별되는 모듈 인스턴스 집합을 캡슐화합니다. 즉 모듈 선언은 모든 위상 레벨이 공유하지만, 모듈 인스턴스는 각 위상마다 구별됩니다. 각 네임스페이스는 **기본 위상(base phase)**을 가지며, 이는 eval이나 dynamic-require 같은 반사 연산이 사용하는 위상에 대응합니다. 특히 require 형식에 eval을 사용하면 네임스페이스의 기본 위상에서 모듈을 인스턴스화합니다.
네임스페이스가 만들어진 뒤, 기존 네임스페이스의 모듈 인스턴스가 새 네임스페이스에 붙을 수 있습니다. 평가 모델의 관점에서, 서로 다른 네임스페이스의 최상위 변수는 본질적으로 서로 다른 접두사를 가진 정의에 대응하지만, 모듈을 붙이는 것은 그 모듈의 정의에 대해 모듈이 붙는 네임스페이스들에서 같은 접두사를 사용합니다. 컴파일된 표현식을 평가하는 첫 단계는 그 최상위 변수 및 모듈-레벨 변수 참조를 네임스페이스의 특정 변수에 연결(link)하는 것입니다.
평가 중 언제나 어떤 네임스페이스가 **현재 네임스페이스(current namespace)**로 지정되어 있습니다. 그러나 현재 네임스페이스는 실행 중인 코드를 확장하는 데 쓰인 네임스페이스나, 현재 평가 중인 코드의 컴파일된 형식을 연결하는 데 쓰인 네임스페이스와 특별한 관계가 없습니다. 특히 평가 중 현재 네임스페이스를 바꿔도 실행 중인 표현식이 참조하는 변수는 바뀌지 않습니다. 현재 네임스페이스는 코드를 확장하고 확장/컴파일된 코드 평가를 시작하는 반사 연산의 동작만 결정합니다.
예:
> (define x 'orig) ; define in the original namespace
; The following let expression is compiled in the original
; namespace, so direct references to x see 'orig.
> (let ([n (make-base-namespace)]) ; make new namespace
(parameterize ([current-namespace n])
(eval '(define x 'new)) ; evals in the new namespace
(display x) ; displays 'orig
(display (eval 'x)))) ; displays 'new
orignew
식별자가 구문이나 임포트에 바인딩되어 있으면, 그 식별자를 변수로 정의하는 것은 환경의 이후 사용에서 그 구문이나 임포트를 가립니다. 비슷하게 식별자가 최상위 변수에 바인딩되어 있으면, 그 식별자를 구문이나 임포트에 바인딩하는 것은 그 변수를 가립니다. 그러나 변수의 값은 변하지 않고, 이전에 평가된 표현식을 통해 접근할 수 있습니다.
예:
> (define x 5)
> (define (f) x)
> x
5
> (f)
5
> (define-syntax x (syntax-id-rules () [_ 10]))
> x
10
> (f)
5
> (define x 7)
> x
7
> (f)
7
> (module m racket (define x 8) (provide x))
> (require 'm)
> x
8
> (f)
7
최상위 네임스페이스처럼, 각 module 형식에는 모듈 내용의 모든 위상 레벨을 아우르는 연관 스코프와, 각 위상 레벨에서 하나씩인 스코프가 있습니다. 후자는 모듈의 직접 본문 안의 모든 형식(원래 것이든 부분 매크로 확장으로 나타난 것이든)에 추가됩니다. 이 스코프들은 그 모듈을 위한 module->namespace로 만들어진 네임스페이스에도 전파됩니다. 한편 module 형식의 파싱은 둘러싼 최상위 또는(서브모듈의 경우) module·module* 형식에 대응하는 모든 스코프를 제거하는 것으로 시작합니다.
1.2.6 추론된 값 이름 (Inferred Value Names)
오류 보고를 개선하기 위해, 프로시저 같은 특정 종류의 값에 대해 컴파일 시간에 이름이 추론됩니다. 예를 들어 다음 표현식을 평가하면:
(let ([f (lambda () 0)]) (f 1 2 3))
프로시저에 너무 많은 인자가 제공되므로 오류 메시지가 생깁니다. 오류 메시지는 프로시저의 이름으로 f를 보고할 수 있습니다. 이 경우 Racket은 컴파일 시간에 let으로 바인딩된 lambda가 만든 모든 프로시저에 'f라는 이름을 붙이기로 결정합니다.
프로시저의 추론된 이름을 런타임에 덮어쓰려면 procedure-rename을 보세요.
가능할 때마다 프로시저에 이름이 추론됩니다. 표현식에 더 가까운 이름이 우선합니다. 예를 들어
(define my-f
(let ([f (lambda () 0)]) f))
에서 my-f에 바인딩된 프로시저는 추론된 이름 'f를 갖게 됩니다.
'inferred-name 속성이 표현식용 구문 객체에 붙어 있으면(Syntax Object Properties 참조), 그 속성 값이 그 표현식의 이름을 붙이는 데 사용되며, 표현식의 문맥에서 추론된 어떤 이름도 덮어씁니다. 보통 속성 값은 심볼이어야 합니다. #<void>인 'inferred-name 속성 값은 (어쩌면 자동 생성된 바인딩에서 온 식별자를 노출하지 않기 위해) 문맥에서 추론되었을 이름을 숨깁니다.
확장 중 일관된 속성의 전파와 병합을 지원하기 위해, 'inferred-name 속성의 값은 모든 잎이 같은 cons로 형성된 트리일 수 있습니다. 예를 들어 (cons 'name 'name)은 'name과 같고, (cons (void) (void))는 #<void>와 같습니다.
추론된 이름을 이용할 수 없지만 소스 위치는 이용 가능하면, 소스 위치 정보로 이름이 구성됩니다. 추론된 이름과 속성으로 지정된 이름은 syntax-local-name을 통해 구문 트랜스포머에도 제공됩니다.
1.2.7 위상 간 영속 모듈 선언 (Cross-Phase Persistent Module Declarations)
모듈이 다음 문법에 맞고(Fully Expanded Programs의 비단말을 사용), (#%declare #:cross-phase-persistent)를 포함하며, quote-syntax나 #%variable-reference의 사용을 포함하지 않고, 모듈-레벨 바인딩이 set!되지 않을 때에만 그 모듈은 **위상 간 영속(cross-phase persistent)**입니다.
cross-module =
(module id module-path
(#%plain-module-begin
cross-form ...))
cross-form = (#%declare #:cross-phase-persistent)
| (begin cross-form ...)
| (#%provide raw-provide-spec ...)
| submodule-form
| (define-values (id ...) cross-expr)
| (#%require raw-require-spec ...)
cross-expr = id
| (quote cross-datum)
| (#%plain-lambda formals expr ...+)
| (case-lambda (formals expr ...+) ...)
| (#%plain-app cons cross-expr ...+)
| (#%plain-app list cross-expr ...+)
| (#%plain-app hasheq cross-expr ...+)
| (#%plain-app make-struct-type cross-expr ...+)
|
(#%plain-app make-struct-type-property
cross-expr ...+)
| (#%plain-app make-parameter cross-expr ...+)
| (#%plain-app gensym)
| (#%plain-app gensym string)
| (#%plain-app string->uninterned-symbol string)
|
(#%plain-app variable-reference-from-unsafe?
(#%variable-reference))
위 문법에서 cross-datum은 대략 The Reader가 표준 read-syntax 설정에서 읽을 수 있는 데이터에 대응하지만, 해시 테이블은 키 비교 술어로 eq?를 쓰는 것(hash-eq? 참조)으로 제한됩니다. 더 정확히 말하면 cross-datum은 숫자, extflonum, 불리언, 심볼, 문자, 키워드, 빈 목록, 문자열, 바이트 문자열, 모든 값도 cross-datum인 벡터, 모든 키와 값도 cross-datum인 hash-eq?, 값도 cross-datum인 box, 또는 모든 필드 값도 cross-datum인 prefab 구조체 중 하나입니다. 게다가 문자열, 바이트 문자열, 벡터, 해시 테이블, box, 구조체는 불변(immutable)이어야 합니다.
이 문법은 확장 이후 적용되지만, 위상 간 영속 모듈은 다른 위상 간 영속 모듈에서만 임포트하므로, 관련된 유일한 확장 단계는 #%plain-module-begin의 암시적 도입, #%plain-app의 암시적 도입, #%datum의 암시적 도입 및/또는 확장, begin 형식의 이어 붙임입니다.
base 패키지 7.5.0.12 버전에서 변경: (#%plain-app variable-reference-from-unsafe? (#%variable-reference))를 허용합니다. 8.15.0.4 버전에서 변경: (#%plain-app hasheq cross-expr ...+)와 (#%plain-app make-parameter cross-expr ...+)를 허용합니다. 9.1.0.4 버전에서 변경: extflonum, 심볼, 문자, 키워드, 벡터, hash-eq?, box, prefab 구조체를 cross-datum으로 허용합니다.