R6RS Scheme: 확장 과정
R6RS Scheme: 확장 과정
매크로 사용이 평가(컴파일 또는 해석) 시작 시점에 핵심 형식으로 확장되는 확장 과정(expansion process)을 설명해요.
출처: 문서
본문
매크로 사용(9.2절 참조)은 문법 확장기(syntax expander)에 의해 평가가 시작될 때(컴파일이나 해석 전에) 핵심 형식(core form) 으로 확장돼요. 핵심 형식의 집합은 구현에 따라 다르며, 확장기 출력에서 이러한 형식들의 표현 방식도 구현에 따라 달라요. 확장기가 문법 추상화를 만나면, 연관된 변환기를 호출해 그 문법 추상화를 확장하고, 변환기가 반환한 형식에 대해 확장 과정을 반복해요. 확장기가 핵심 형식을 만나면, 표현식 또는 정의 문맥에 있는 그 부분 형식들(있다면)을 재귀적으로 처리하고, 확장된 부분 형식들로부터 형식을 재구성해요. 확장 중에는 변수와 키워드에 대한 어휘적 유효범위를 강제하기 위해 식별자 바인딩에 대한 정보가 유지돼요.
정의를 처리하기 위해, 확장기는 <body>(11.3절 참조) 또는 <library body>(7.1절 참조)의 초기 형식들을 왼쪽에서 오른쪽으로 처리해요. 확장기가 만나는 각 형식을 처리하는 방식은 형식의 종류에 따라 달라요.
매크로 사용 — 확장기는 연관된 변환기를 호출해 매크로 사용을 변환한 다음, 결과로 나온 형식에 적절한 다음 작업들을 재귀적으로 수행해요.
define-syntax 형식 — 확장기는 오른쪽의 표현식을 확장하고 평가하여 키워드를 결과로 얻은 변환기에 바인딩해요.
define 형식 — 확장기는 정의된 식별자가 변수라는 사실을 기록하지만, 모든 정의가 처리된 후까지 오른쪽 표현식의 확장을 연기해요.
begin 형식 — 확장기는 부분 형식들을 처리 중인 body 형식들의 목록에 접합(splice)해요. (11.4.7절 참조.)
let-syntax 또는 letrec-syntax 형식 — 확장기는 안쪽 body 형식들을 (바깥쪽) body 형식들의 목록에 접합하고, let-syntax와 letrec-syntax에 의해 바인딩된 키워드들이 안쪽 body 형식들에서만 보이도록 배치해요.
표현식, 즉 비정의(nondefinition) — 확장기는 연기된 오른쪽 표현식들과 현재 및 나머지 body 표현식들의 확장을 완료한 다음, 정의된 변수들, 확장된 오른쪽 표현식들, 확장된 body 표현식들로부터 letrec* 형식과 동등한 것을 만들어요.
변수 정의의 오른쪽에 대해서는, 확장이 모든 정의가 보일 때까지 연기돼요. 결과적으로, 오른쪽 안의 각 키워드와 변수 참조는, 있다면, 지역 바인딩으로 해석돼요.
형식들의 수열에 있는 정의는, 그 바인딩이 정의의 연기되지 않은 부분이나 그 수열에서 앞에 오는 정의의 의미를 결정하는 데 사용되는 어떤 식별자도 정의해서는 안 돼요. 예를 들어, 다음 표현식들의 body는 이 제한을 위반해요.
(let ()
(define define 17)
(list define))
(let-syntax ([def0 (syntax-rules ()
[(_ x) (define x 0)])])
(let ([z 3])
(def0 z)
(define def0 list)
(list z)))
(let ()
(define-syntax foo
(lambda (e)
(+ 1 2)))
(define + 2)
(foo))
다음은 이 제한을 위반하지 않아요.
(let ([x 5])
(define lambda list)
(lambda x x)) ⇒ (5 5)
(let-syntax ([def0 (syntax-rules ()
[(_ x) (define x 0)])])
(let ([z 3])
(define def0 list)
(def0 z)
(list z))) ⇒ (3)
(let ()
(define-syntax foo
(lambda (e)
(let ([+ -]) (+ 1 2))))
(define + 2)
(foo)) ⇒ -1
구현은 제한의 위반을 문법 위반(syntax violation)으로 처리해야 해요.
이 알고리즘은 어떤 형식도 직접 재처리하지 않는다는 점에 주목해요. 정의들에 대한 단일 왼쪽-오른쪽 전달 후 body 표현식들과 연기된 오른쪽들에 대한 단일 전달(어느 순서든)을 요구해요.
예:
(lambda (x)
(define-syntax defun
(syntax-rules ()
[(_ x a e) (define x (lambda a e))]))
(defun even? (n) (or (= n 0) (odd? (- n 1))))
(define-syntax odd?
(syntax-rules () [(_ n) (not (even? n))]))
(odd? (if (odd? x) (* x x) x)))
예에서, defun의 정의가 먼저 만나지고, 키워드 defun은 대응하는 오른쪽의 확장과 평가로부터 얻은 변환기에 연결돼요. defun의 사용이 다음에 만나지고 define 형식으로 확장돼요. 이 define 형식의 오른쪽 확장은 연기돼요. odd?의 정의가 그 다음이고, 키워드 odd?를 대응하는 오른쪽을 확장·평가하여 얻은 변환기에 연결하는 결과를 낳아요. odd?의 사용이 다음에 나타나고 확장돼요; not이 변수로 바인딩되어 있으므로 결과로 나온 not 호출은 표현식으로 인식돼요. 이 시점에서 확장기는 현재 표현식(not 호출)과 even? 정의의 연기된 오른쪽의 확장을 완료해요; 이 표현식들에 나타나는 odd?의 사용들은 키워드 odd?와 연결된 변환기를 사용해 확장돼요. 최종 출력은 다음과 동등해요.
(lambda (x)
(letrec* ([even?
(lambda (n)
(or (= n 0)
(not (even? (- n 1)))))])
(not (even? (if (not (even? x)) (* x x) x)))))
출력의 구조는 구현에 따라 다르긴 해요.
정의와 표현식이 <top-level body>(8장 참조)에서 서로 섞일 수 있으므로, <top-level body>에 대한 확장기의 처리는 다소 더 복잡해요. 그것은 <body> 또는 <library body>에 대해 위에서 설명한 것처럼 동작하되 다음 예외들이 있어요: 확장기가 비정의(nondefinition)를 찾으면 확장을 연기하고 정의를 계속 찾아요. 형식들의 집합의 끝에 도달하면 연기된 오른쪽과 body 표현식들을 처리한 다음, 정의된 변수들, 확장된 오른쪽 표현식들, 확장된 body 표현식들로부터 letrec* 형식과 동등한 것을 생성해요. body에서 변수 정의 앞에 나타나는 각 body 표현식 <expression>에 대해, letrec* 바인딩들의 집합 안의 대응하는 위치에 더미(dummy) 바인딩이 만들어져요. 왼쪽에는 새로운 임시 변수를, 오른쪽에는 다음과 동등한 것의 오른쪽을 두어서 왼쪽-오른쪽 평가 순서가 보존되게 해요:
(begin <expression>
<unspecified>)
여기서 <unspecified>는 미지정 값을 반환하는 부작용 없는 표현식이에요. begin 감싸기는 <expression>이 임의의 개수의 값으로 평가될 수 있게 해줘요.