(Lispex)sicp.io
4.13 · 평가기를 프로그램으로 실행하기

평가기는 유한한 프로그램을 읽고 환경을 보존할 수 있다.

작은 eval/apply 체계는 인용된 최상위 형식을 하나의 가변 전역 환경에서 평가하고 정의와 값의 transcript를 반환하며 명시적인 형식 예산이 배치를 끝냈는지 보고합니다.

생각해 볼 질문

평가기 프로시저 모음을 사용자 형식의 수열을 실행하는 하나의 프로그램으로 만드는 것은 무엇일까요?

  • 호스트 리스펙스 실행과 데이터로 표현한 guest 식 구분하기
  • 자기 평가 값과 변수와 특수 형식과 적용 디스패치하기
  • 호스트 원시 프로시저와 표현된 복합 프로시저 적용하기
  • 하나의 명시적인 전역 환경에 정의와 대입 보존하기
  • 나중의 전역 변경 뒤에도 어휘 클로저가 캡처한 값 관찰하기
  • 유한한 최상위 드라이버 실행을 complete 또는 truncated로 보고하기
  • 최상위 형식 예산과 한 형식 안의 작업량 구분하기

평가기는 환경을 가변 프레임의 리스트로 표현합니다. guest 변수 조회는 그 프레임을 검색하고 define은 첫 프레임을 바꾸며 set!은 기존 바인딩을 찾아 그 레코드를 바꿉니다. guest lambda는 매개변수와 본문 식과 lambda가 평가된 환경을 담은 복합 프로시저 레코드가 됩니다. apply-procedure는 호스트 원시 프로시저를 적용하거나 새 프레임에서 복합 본문을 평가합니다.

run-program이 드라이버입니다. 인용된 최상위 형식과 하나의 전역 환경과 형식 예산을 받습니다. 끝난 형식마다 transcript 값 하나를 남기며 정의와 대입은 뒤 형식에서도 보입니다. 전체 실행은 square와 make-adder와 add-five와 base를 정의합니다. add-five는 전역 base가 바뀐 뒤에도 지역 x 값 5를 유지합니다. 두 번째 실행은 다섯 형식 뒤에 멈추고 형식 네 개가 남았다고 보고합니다. 이 형식 예산은 한 형식 안의 재귀나 작업량을 제한하지 않으며 그 아래 실행 경계는 패키지된 브라우저 런타임이 담당합니다.

리스펙스 · SICP 코드SICP에 필요한 Scheme 호환 문법을 리스펙스 SICP 프로필로 실행합니다.
(begin
  (define (tagged-list? expression tag)
    (if (pair? expression)
        (eq? (car expression) tag)
        #f))
  (define (self-evaluating? expression)
    (cond ((number? expression) #t)
          ((string? expression) #t)
          ((boolean? expression) #t)
          (else #f)))
  (define (quoted? expression)
    (tagged-list? expression 'quote))
  (define (assignment? expression)
    (tagged-list? expression 'set!))
  (define (definition? expression)
    (tagged-list? expression 'define))
  (define (if? expression)
    (tagged-list? expression 'if))
  (define (lambda? expression)
    (tagged-list? expression 'lambda))
  (define (begin? expression)
    (tagged-list? expression 'begin))

  (define (text-of-quotation expression) (cadr expression))
  (define (assignment-variable expression) (cadr expression))
  (define (assignment-value expression) (caddr expression))
  (define (definition-variable expression)
    (if (symbol? (cadr expression))
        (cadr expression)
        (car (cadr expression))))
  (define (definition-value expression)
    (if (symbol? (cadr expression))
        (caddr expression)
        (cons 'lambda
              (cons (cdr (cadr expression))
                    (cddr expression)))))
  (define (if-predicate expression) (cadr expression))
  (define (if-consequent expression) (caddr expression))
  (define (if-alternative expression) (cadddr expression))
  (define (lambda-parameters expression) (cadr expression))
  (define (lambda-body expression) (cddr expression))
  (define (begin-actions expression) (cdr expression))
  (define (operator expression) (car expression))
  (define (operands expression) (cdr expression))

  (define (pair-bindings variables values)
    (cond ((and (null? variables) (null? values)) '())
          ((null? variables) (error "too many arguments"))
          ((null? values) (error "too few arguments"))
          (else
           (cons (cons (car variables) (car values))
                 (pair-bindings (cdr variables)
                                (cdr values))))))
  (define (make-frame variables values)
    (cons '*frame* (pair-bindings variables values)))
  (define (first-frame environment) (car environment))
  (define (frame-bindings frame) (cdr frame))
  (define (extend-environment variables values base)
    (cons (make-frame variables values) base))
  (define (lookup-variable-value variable environment)
    (if (null? environment)
        (error "unbound variable" variable)
        (let ((binding
               (assoc variable
                      (frame-bindings (first-frame environment)))))
          (if binding
              (cdr binding)
              (lookup-variable-value variable (cdr environment))))))
  (define (define-variable! variable value environment)
    (let* ((frame (first-frame environment))
           (binding (assoc variable (frame-bindings frame))))
      (if binding
          (set-cdr! binding value)
          (set-cdr! frame
                    (cons (cons variable value)
                          (frame-bindings frame)))))
    (list 'defined variable))
  (define (set-variable-value! variable value environment)
    (if (null? environment)
        (error "unbound assignment" variable)
        (let ((binding
               (assoc variable
                      (frame-bindings (first-frame environment)))))
          (if binding
              (begin
                (set-cdr! binding value)
                (list 'assigned variable))
              (set-variable-value! variable value (cdr environment))))))

  (define (make-procedure parameters body environment)
    (list 'compound parameters body environment))
  (define (compound-procedure? procedure)
    (tagged-list? procedure 'compound))
  (define (procedure-parameters procedure) (cadr procedure))
  (define (procedure-body procedure) (caddr procedure))
  (define (procedure-environment procedure) (cadddr procedure))

  (define (list-of-values expressions environment)
    (if (null? expressions)
        '()
        (cons (evaluate (car expressions) environment)
              (list-of-values (cdr expressions) environment))))
  (define (eval-sequence expressions environment)
    (if (null? (cdr expressions))
        (evaluate (car expressions) environment)
        (begin
          (evaluate (car expressions) environment)
          (eval-sequence (cdr expressions) environment))))
  (define (eval-if expression environment)
    (if (evaluate (if-predicate expression) environment)
        (evaluate (if-consequent expression) environment)
        (evaluate (if-alternative expression) environment)))
  (define (eval-assignment expression environment)
    (set-variable-value!
      (assignment-variable expression)
      (evaluate (assignment-value expression) environment)
      environment))
  (define (eval-definition expression environment)
    (define-variable!
      (definition-variable expression)
      (evaluate (definition-value expression) environment)
      environment))
  (define (apply-procedure procedure arguments)
    (cond ((procedure? procedure)
           (apply procedure arguments))
          ((compound-procedure? procedure)
           (eval-sequence
             (procedure-body procedure)
             (extend-environment
               (procedure-parameters procedure)
               arguments
               (procedure-environment procedure))))
          (else
           (error "not a guest procedure" procedure))))
  (define (evaluate expression environment)
    (cond ((self-evaluating? expression) expression)
          ((symbol? expression)
           (lookup-variable-value expression environment))
          ((quoted? expression)
           (text-of-quotation expression))
          ((assignment? expression)
           (eval-assignment expression environment))
          ((definition? expression)
           (eval-definition expression environment))
          ((if? expression)
           (eval-if expression environment))
          ((lambda? expression)
           (make-procedure
             (lambda-parameters expression)
             (lambda-body expression)
             environment))
          ((begin? expression)
           (eval-sequence (begin-actions expression) environment))
          ((pair? expression)
           (apply-procedure
             (evaluate (operator expression) environment)
             (list-of-values (operands expression) environment)))
          (else
           (error "unknown guest expression" expression))))

  (define primitive-bindings
    (list (cons '+ +) (cons '- -) (cons '* *) (cons '/ /)
          (cons '= =) (cons '< <) (cons '> >)
          (cons 'cons cons) (cons 'car car) (cons 'cdr cdr)
          (cons 'list list) (cons 'null? null?)
          (cons 'pair? pair?) (cons 'not not)))
  (define (make-global-environment)
    (list (cons '*frame* primitive-bindings)))
  (define (run-program forms environment form-budget)
    (define (loop remaining budget transcript)
      (cond ((null? remaining)
             (list 'complete (reverse transcript) 0))
            ((= budget 0)
             (list 'truncated
                   (reverse transcript)
                   (length remaining)))
            (else
             (loop (cdr remaining)
                   (- budget 1)
                   (cons (evaluate (car remaining) environment)
                         transcript)))))
    (loop forms form-budget '()))

  (define guest-program
    '((define (square x) (* x x))
      (define (make-adder x) (lambda (y) (+ x y)))
      (define add-five (make-adder 5))
      (define base 5)
      (square (+ base 2))
      (add-five 7)
      (set! base 9)
      (if (> base 8) (square base) 0)
      (add-five 1)))
  (list
    (run-program guest-program (make-global-environment) 20)
    (run-program guest-program (make-global-environment) 5)))
리스펙스 학습용 런타임리스펙스 SICP 프로필 1.0.0
리스펙스 SICP 런타임 불러오는 중
리스펙스 · SICP 코드UTF-8 7,837 / 1,048,576바이트
예제
결과
출력
진단
보이는 실행 흐름0 / 0 개의 실행 이벤트
    이 브라우저 결과는 리스펙스 바우치나 권한이 아닙니다.wasm —
    예상 관찰

    전체 실행은 (complete ((defined square) (defined make-adder) (defined add-five) (defined base) 49 12 (assigned base) 81 6) 0)을 반환합니다. 제한한 실행은 (truncated ((defined square) (defined make-adder) (defined add-five) (defined base) 49) 4)를 반환합니다.

    실행 흐름에서 볼 점

    evaluate와 apply-procedure를 구현하는 호스트 호출과 guest-program에 저장된 guest 식을 구분하세요. define과 set!에 따른 전역 프레임 변경, 복합 적용마다 만든 새 프레임, add-five가 캡처한 환경을 따라가세요. run-program의 예산은 최상위 형식 하나가 반환된 뒤에만 한 단위 줄며 그 형식 내부의 재귀 작업을 재는 계수기가 아닙니다.

    직접 해보기

    힌트를 보기 전에 프로그램을 바꿔 보세요.

    guest에 (define (twice procedure value) (procedure (procedure value)))를 추가하고 (twice add-five 1)을 평가하세요. 새 transcript 값과 형식 예산을 5로 유지했을 때 남는 형식 수를 예상하세요.

    힌트 하나 보기

    전체 실행은 add-five를 두 번 적용하므로 guest 결과가 11입니다. 제한 실행에서는 기존 정의 뒤에 형식을 어디에 끼워 넣는지에 따라 다섯 번째로 끝나는 식이 달라집니다.