평가기는 지역 초기화를 명시적인 형태로 바꿀 수 있다.
프로시저 본문 변환은 내부 이름을 모두 먼저 만들고 대입으로 각 값을 설치한 뒤 남은 본문을 보존해 서로 재귀적인 helper가 하나의 지역 환경을 공유하게 합니다.
평가기는 왜 프로시저 값을 하나라도 설치하기 전에 내부 바인딩을 모두 먼저 만들어야 할까요?
- 프로시저 본문 앞쪽의 내부 정의 알아보기
- 프로시저 정의 축약형을 명시적인 lambda 값으로 바꾸기
- 대입 전에 모든 지역 이름을 unassigned 표식으로 만들기
- 대입 뒤에 정의가 아닌 원래 본문 순서 보존하기
- 원래 프로그램과 변환한 프로그램을 같은 유한 입력으로 비교하기
- 관찰한 일치와 모든 프로그램·구현에 대한 증명을 구분하기
scan-out 단계는 프로시저 본문을 데이터로 읽습니다. 앞쪽 define 형식을 모으고 프로시저 정의 축약형을 lambda 식으로 바꾸며, 각 이름을 명시적인 unassigned 표식으로 갖는 let 바인딩을 먼저 만듭니다. 그다음 각 정의의 set!을 놓고 마지막에 원래 남은 본문을 붙입니다. 따라서 어느 클로저가 적용되기 전에 이름들이 한 환경 안에 함께 존재해 서로 재귀적인 helper가 같은 지역 환경을 공유합니다.
두 번째 프로그램은 두 형태를 실제로 실행합니다. classify-original은 내부 정의를 그대로 사용하고 classify-scanned는 변환된 let과 set! 구조를 명시적으로 씁니다. 7과 8에서 같은 결과가 나오며 equal?은 참을 보고합니다. 이 유한 비교는 이번 변환과 입력을 확인할 뿐 모든 Scheme 프로그램의 의미 동등성을 증명하거나 숨은 리스펙스 컴파일 단계를 드러내거나 모든 구현의 중간 표현을 규정하지 않습니다.
(begin
(define body
'((define (even-step value)
(if (= value 0)
#t
(odd-step (- value 1))))
(define (odd-step value)
(if (= value 0)
#f
(even-step (- value 1))))
(list (even-step n) (odd-step n))))
(define (definition? expression)
(and (pair? expression)
(eq? (car expression) 'define)))
(define (take-definitions expressions)
(if (and (pair? expressions)
(definition? (car expressions)))
(cons (car expressions)
(take-definitions (cdr expressions)))
'()))
(define (drop-definitions expressions)
(if (and (pair? expressions)
(definition? (car expressions)))
(drop-definitions (cdr expressions))
expressions))
(define (definition-name definition)
(if (symbol? (cadr definition))
(cadr definition)
(car (cadr definition))))
(define (definition-value definition)
(if (symbol? (cadr definition))
(caddr definition)
(cons 'lambda
(cons (cdr (cadr definition))
(cddr definition)))))
(define (make-binding definition)
(list (definition-name definition)
(list 'quote '*unassigned*)))
(define (make-assignment definition)
(list 'set!
(definition-name definition)
(definition-value definition)))
(define (scan-out-defines expressions)
(let ((definitions (take-definitions expressions))
(remaining (drop-definitions expressions)))
(if (null? definitions)
expressions
(list
(cons 'let
(cons (map make-binding definitions)
(append (map make-assignment definitions)
remaining)))))))
(car (scan-out-defines body)))- 출력
- —
- 값
- —
- 진단
- —
변환 프로그램은 even-step과 odd-step을 인용된 *unassigned* 표식에 바인딩한 let 식 하나와 두 set! 형식, 원래 list 본문을 반환합니다. 비교 프로그램은 (#t ((#f #t) (#t #f)) ((#f #t) (#t #f)))를 반환합니다.
변환 실행에서는 인용된 소스 데이터를 읽는 일과 앞쪽 정의 모으기, 바인딩 만들기, 프로시저 정의를 lambda 값으로 바꾸기, 대입 만들기, 남은 본문 붙이기를 구분하세요. 비교 실행에서는 하나의 let 환경과 두 set!, 번갈아 호출되는 클로저를 찾으세요. 일치는 선택한 입력에 대해서만 보고됩니다.
힌트를 보기 전에 프로그램을 바꿔 보세요.
세 개의 서로 재귀적인 나머지 프로시저로 3의 배수를 알아보는 내부 helper를 추가하세요. 원래 버전과 명시적으로 scan-out한 버전을 모두 쓰고 8과 9의 결과를 비교하세요.
힌트 하나 보기
대입 전에 이름 세 개를 모두 만드세요. 각 클로저는 1을 빼고 다음 나머지 프로시저를 호출하며 나머지 0 프로시저만 0에서 참을 반환합니다.