(Lispex)sicp.io
4.14 · amb 평가기 구현하기

실패는 저장해 둔 다음 대안을 다시 시작한다.

명시적인 평가기는 guest 평가 전체에 성공 continuation과 실패 continuation을 함께 전달할 수 있습니다. amb는 남은 선택을 실패 경로에 저장하고 require는 후보가 제약을 어기면 그 경로를 호출합니다.

생각해 볼 질문

평가기는 실패를 종료 오류가 아니라 이전 선택점의 다음 대안을 다시 시작하라는 요청으로 어떻게 바꿀까요?

  • 명시적인 성공·실패 continuation으로 guest 식 평가하기
  • 어휘 환경을 가진 guest 복합 프로시저 나타내기
  • 연산자와 피연산자 평가 전체에 대안 continuation 전달하기
  • 남은 선택으로 이어지는 실패 경로를 사용해 amb 구현하기
  • 술어가 거짓일 때 현재 대안을 호출해 require 구현하기
  • 모든 유한 해를 모으거나 명시적인 해 수 한도에서 멈추기
  • 빠진 대입 되돌리기와 공정성 정책을 경계로 남기기

ambeval은 expression과 environment와 succeed와 fail을 받습니다. 결정적인 식은 값과 함께 나중 일이 그 값을 거부할 때 사용할 실패 continuation을 succeed에 넘깁니다. amb 형식은 첫 선택을 평가하면서 실패 continuation을 남은 선택을 시도하는 프로시저로 바꿉니다. 피연산자 평가는 이 continuation을 왼쪽에서 오른쪽으로 이어 전달하므로 프로시저 본문의 실패가 앞에서 고른 비결정적 인자의 다음 값으로 돌아갈 수 있습니다.

require는 같은 continuation 체계 안에서 술어를 평가합니다. 참이면 ok로 성공하고 거짓이면 다음 술어 대안을 호출하며 결국 가장 가까운 amb 선택점으로 돌아갑니다. all-values는 성공마다 받은 다음 대안을 계속 호출합니다. 첫 실행은 유한 탐색을 모두 소진해 complete를 보고하고 두 번째는 해 네 개 뒤에 일부러 멈춰 truncated를 보고합니다. 이 수업은 되돌릴 수 있는 set!과 permanent-set!, 무작위 선택 순서, 중복 제거, 무한 탐색 공간의 공정성을 구현하지 않습니다.

리스펙스 · SICP 코드SICP에 필요한 Scheme 호환 문법을 리스펙스 SICP 프로필로 실행합니다.
(begin
  (define (tagged-list? expression tag)
    (and (pair? expression) (eq? (car expression) tag)))
  (define (self-evaluating? expression)
    (or (number? expression)
        (string? expression)
        (boolean? expression)))
  (define (quoted? expression) (tagged-list? expression 'quote))
  (define (if? expression) (tagged-list? expression 'if))
  (define (lambda? expression) (tagged-list? expression 'lambda))
  (define (begin? expression) (tagged-list? expression 'begin))
  (define (amb? expression) (tagged-list? expression 'amb))
  (define (require? expression) (tagged-list? expression 'require))

  (define (text-of-quotation expression) (cadr 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 (amb-choices expression) (cdr expression))
  (define (require-predicate expression) (cadr 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 (extend-environment variables values environment)
    (append (pair-bindings variables values) environment))
  (define (lookup-variable-value variable environment)
    (let ((binding (assoc variable environment)))
      (if binding
          (cdr binding)
          (error "unbound guest variable" variable))))

  (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 (eval-sequence expressions environment succeed fail)
    (cond ((null? expressions) (succeed 'ok fail))
          ((null? (cdr expressions))
           (ambeval (car expressions) environment succeed fail))
          (else
           (ambeval
             (car expressions)
             environment
             (lambda (ignored next-alternative)
               (eval-sequence
                 (cdr expressions)
                 environment
                 succeed
                 next-alternative))
             fail))))

  (define (get-arguments expressions environment succeed fail)
    (if (null? expressions)
        (succeed '() fail)
        (ambeval
          (car expressions)
          environment
          (lambda (argument next-argument)
            (get-arguments
              (cdr expressions)
              environment
              (lambda (remaining next-remaining)
                (succeed (cons argument remaining) next-remaining))
              next-argument))
          fail)))

  (define (apply-procedure procedure arguments succeed fail)
    (cond ((procedure? procedure)
           (succeed (apply procedure arguments) fail))
          ((compound-procedure? procedure)
           (eval-sequence
             (procedure-body procedure)
             (extend-environment
               (procedure-parameters procedure)
               arguments
               (procedure-environment procedure))
             succeed
             fail))
          (else
           (error "not a guest procedure" procedure))))

  (define (eval-if expression environment succeed fail)
    (ambeval
      (if-predicate expression)
      environment
      (lambda (predicate-value next-predicate)
        (ambeval
          (if predicate-value
              (if-consequent expression)
              (if-alternative expression))
          environment
          succeed
          next-predicate))
      fail))

  (define (eval-require expression environment succeed fail)
    (ambeval
      (require-predicate expression)
      environment
      (lambda (predicate-value next-predicate)
        (if predicate-value
            (succeed 'ok next-predicate)
            (next-predicate)))
      fail))

  (define (eval-amb choices environment succeed fail)
    (define (try-next remaining)
      (if (null? remaining)
          (fail)
          (ambeval
            (car remaining)
            environment
            succeed
            (lambda () (try-next (cdr remaining))))))
    (try-next choices))

  (define (eval-application expression environment succeed fail)
    (ambeval
      (operator expression)
      environment
      (lambda (procedure next-operator)
        (get-arguments
          (operands expression)
          environment
          (lambda (arguments next-arguments)
            (apply-procedure
              procedure
              arguments
              succeed
              next-arguments))
          next-operator))
      fail))

  (define (ambeval expression environment succeed fail)
    (cond ((self-evaluating? expression)
           (succeed expression fail))
          ((symbol? expression)
           (succeed
             (lookup-variable-value expression environment)
             fail))
          ((quoted? expression)
           (succeed (text-of-quotation expression) fail))
          ((if? expression)
           (eval-if expression environment succeed fail))
          ((lambda? expression)
           (succeed
             (make-procedure
               (lambda-parameters expression)
               (lambda-body expression)
               environment)
             fail))
          ((begin? expression)
           (eval-sequence
             (begin-actions expression)
             environment
             succeed
             fail))
          ((amb? expression)
           (eval-amb
             (amb-choices expression)
             environment
             succeed
             fail))
          ((require? expression)
           (eval-require expression environment succeed fail))
          ((pair? expression)
           (eval-application expression environment succeed fail))
          (else
           (error "unknown guest expression" expression))))

  (define primitive-environment
    (list (cons '+ +) (cons '- -) (cons '* *) (cons '/ /)
          (cons '= =) (cons '< <) (cons '> >)
          (cons '<= <=) (cons '>= >=)
          (cons 'list list) (cons 'cons cons)
          (cons 'car car) (cons 'cdr cdr)
          (cons 'null? null?) (cons 'pair? pair?)
          (cons 'not not) (cons 'abs abs)))

  (define (all-values expression solution-limit)
    (if (= solution-limit 0)
        (list 'truncated '())
        (let ((answers '())
              (count 0))
          (ambeval
            expression
            primitive-environment
            (lambda (value next-alternative)
              (set! answers (cons value answers))
              (set! count (+ count 1))
              (if (= count solution-limit)
                  (list 'truncated (reverse answers))
                  (next-alternative)))
            (lambda ()
              (list 'complete (reverse answers)))))))
  (define pair-program
    '((lambda (left right)
        (begin
          (require (< left right))
          (require (= (+ left right) 5))
          (list left right)))
      (amb 1 2 3 4)
      (amb 1 2 3 4)))
  (all-values pair-program 20)
)
리스펙스 학습용 런타임리스펙스 SICP 프로필 1.0.0
리스펙스 SICP 런타임 불러오는 중
리스펙스 · SICP 코드UTF-8 7,667 / 1,048,576바이트
예제
결과
출력
진단
보이는 실행 흐름0 / 0 개의 실행 이벤트
    이 브라우저 결과는 리스펙스 바우치나 권한이 아닙니다.wasm —
    예상 관찰

    순서쌍 탐색은 (complete ((1 4) (2 3)))을 반환합니다. 한도를 둔 세 수 탐색은 (truncated ((1 2 3) (1 2 4) (1 2 5) (1 3 4)))를 반환합니다.

    실행 흐름에서 볼 점

    각 amb 선택이 남은 선택을 위한 실패 continuation을 설치하는 곳을 찾으세요. 그다음 본문의 require가 후보를 거부할 때 get-arguments가 뒤의 third 인자와 second 인자와 first 인자의 다음 대안으로 차례로 돌아가는 과정을 따라가세요. 해 수 한도는 남은 탐색이 비었다고 주장하지 않고 멈춥니다.

    직접 해보기

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

    순서쌍 프로그램의 left와 right를 1부터 6에서 고르고 곱이 12이며 left가 더 작은 모든 해를 모으도록 바꾸세요. 실행 전에 해의 순서를 예상하세요.

    힌트 하나 보기

    왼쪽에서 오른쪽으로 진행하는 amb 순서는 left가 1일 때 모든 right를 시험한 뒤 left 2로 이동합니다. 두 require를 모두 만족하는 순서가 있는 약수쌍만 남습니다.