(Lispex)sicp.io
2.12 · 서로 다른 타입의 데이터 결합하기

범용 연산은 값을 공통 표현으로 옮길 수 있다.

방향이 있는 coercion 표는 알려진 혼합 타입 한 쌍을 처리하고, 수치 tower는 정수를 유리수로, 유리수를 복소수로 반복해서 올린 뒤 같은 타입 연산 하나를 적용합니다.

생각해 볼 질문

범용 산술은 실패한 coercion을 숨기거나 동급 표현 사이에서 무한히 왕복하지 않으면서 공통 타입을 어떻게 고를까요?

  • 연산 메서드와 coercion 프로시저 구분하기
  • 방향이 있는 coercion 뒤 범용 연산 다시 시도하기
  • 정수–유리수–복소수 계층을 따라 값 올리기
  • 두 인자가 같은 타입이 된 뒤에만 산술 메서드 적용하기
  • 공통 타입이 없으면 추측하지 않고 실패 상태 보고하기

첫 프로그램에는 유리수 내용 두 개를 더하는 산술 메서드 하나만 있습니다. 정수와 유리수 쌍에는 직접 메서드가 없으므로 apply-generic은 별도 coercion 표를 확인하고 정수를 분모 1인 유리수로 바꾼 뒤 다시 시도합니다. coercion 기록은 추가 표현 단계를 보이게 합니다. 같은 타입 메서드가 없을 때 같은 타입 coercion을 시도하지 않으므로 진전 없는 반복도 피합니다.

두 번째 프로그램은 타입 쌍마다 변환을 고르는 대신 순서가 있는 tower를 사용합니다. rank는 정수와 유리수와 복소수의 층을 정하고, raise는 바로 다음 층으로만 이동하며, raise-to는 두 값이 더 높은 입력 층에 닿을 때까지 반복합니다. 정수와 유리수는 유리수 덧셈을 사용하고 유리수와 복소수는 한 번 더 올린 뒤 복소수 덧셈을 사용합니다. 관련 없는 polynomial 태그는 rank가 없으므로 no-common-type을 반환합니다. tower는 이 순서가 있는 타입의 모호성을 줄이지만 모든 데이터 타입이 하나의 계층에 들어간다거나 아래로 내리는 일이 항상 손실 없다는 뜻은 아닙니다.

리스펙스 · SICP 코드SICP에 필요한 Scheme 호환 문법을 리스펙스 SICP 프로필로 실행합니다.
(begin
  (define (attach-tag type contents) (cons type contents))
  (define (type-tag object) (car object))
  (define (contents object) (cdr object))
  (define (lookup-entry key table)
    (cond ((null? table) #f)
          ((equal? key (car (car table)))
           (cdr (car table)))
          (else (lookup-entry key (cdr table)))))
  (define (make-integer value)
    (attach-tag 'integer value))
  (define (make-rational numerator denominator)
    (attach-tag 'rational (list numerator denominator)))
  (define (add-rational-contents left right)
    (make-rational
      (+ (* (car left) (cadr right))
         (* (car right) (cadr left)))
      (* (cadr left) (cadr right))))
  (define operation-table
    (list
      (cons (list 'add 'rational 'rational)
            add-rational-contents)))
  (define coercion-log '())
  (define (integer->rational object)
    (set! coercion-log
          (cons '(integer rational) coercion-log))
    (make-rational (contents object) 1))
  (define coercion-table
    (list
      (cons (list 'integer 'rational)
            integer->rational)))
  (define (apply-generic operation left right)
    (let* ((left-type (type-tag left))
           (right-type (type-tag right))
           (method
             (lookup-entry
               (list operation left-type right-type)
               operation-table)))
      (cond (method
             (method (contents left) (contents right)))
            ((eq? left-type right-type) 'no-method)
            (else
             (let ((left->right
                     (lookup-entry
                       (list left-type right-type)
                       coercion-table))
                   (right->left
                     (lookup-entry
                       (list right-type left-type)
                       coercion-table)))
               (cond (left->right
                      (apply-generic operation
                                     (left->right left)
                                     right))
                     (right->left
                      (apply-generic operation
                                     left
                                     (right->left right)))
                     (else 'no-method)))))))
  (define result
    (apply-generic 'add
                   (make-integer 3)
                   (make-rational 1 2)))
  (list result (reverse coercion-log)))
리스펙스 학습용 런타임리스펙스 SICP 프로필 1.0.0
리스펙스 SICP 런타임 불러오는 중
리스펙스 · SICP 코드UTF-8 2,373 / 1,048,576바이트
예제
결과
출력
진단
보이는 실행 흐름0 / 0 개의 실행 이벤트
    이 브라우저 결과는 리스펙스 바우치나 권한이 아닙니다.wasm —
    예상 관찰

    coercion 표 프로그램은 ((rational 7 2) ((integer rational)))을 반환합니다. tower 프로그램은 ((rational 5 2) (complex #t #t) no-common-type)을 반환합니다.

    실행 흐름에서 볼 점

    첫 실행에서는 실패한 혼합 타입 조회와 integer-to-rational 변환, 성공한 rational/rational 재시도를 찾으세요. tower 실행에서는 한 단계 정수 올리기와 유리수에서 복소수로 올리기를 비교하고 polynomial 태그에서는 산술 메서드를 실행하기 전에 no-common-type이 나오는지 확인하세요. 이 실행 흐름은 명시한 변환만 기록하며 모든 타입 시스템의 정본 계층을 주장하지 않습니다.

    직접 해보기

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

    유리수와 복소수 사이에 real 타입을 추가하세요. 유리수를 real로 올린 뒤 복소수로 올리고, 정수와 복소수를 더할 때 raise가 몇 번 필요한지 예상하세요.

    힌트 하나 보기

    rank를 갱신하고 raise 하나가 정확히 한 층만 이동하게 하세요. 범용 연산에는 새로운 integer/complex 전용 coercion이 필요하지 않아야 합니다.