범용 연산은 값을 공통 표현으로 옮길 수 있다.
방향이 있는 coercion 표는 알려진 혼합 타입 한 쌍을 처리하고, 수치 tower는 정수를 유리수로, 유리수를 복소수로 반복해서 올린 뒤 같은 타입 연산 하나를 적용합니다.
범용 산술은 실패한 coercion을 숨기거나 동급 표현 사이에서 무한히 왕복하지 않으면서 공통 타입을 어떻게 고를까요?
- 연산 메서드와 coercion 프로시저 구분하기
- 방향이 있는 coercion 뒤 범용 연산 다시 시도하기
- 정수–유리수–복소수 계층을 따라 값 올리기
- 두 인자가 같은 타입이 된 뒤에만 산술 메서드 적용하기
- 공통 타입이 없으면 추측하지 않고 실패 상태 보고하기
첫 프로그램에는 유리수 내용 두 개를 더하는 산술 메서드 하나만 있습니다. 정수와 유리수 쌍에는 직접 메서드가 없으므로 apply-generic은 별도 coercion 표를 확인하고 정수를 분모 1인 유리수로 바꾼 뒤 다시 시도합니다. coercion 기록은 추가 표현 단계를 보이게 합니다. 같은 타입 메서드가 없을 때 같은 타입 coercion을 시도하지 않으므로 진전 없는 반복도 피합니다.
두 번째 프로그램은 타입 쌍마다 변환을 고르는 대신 순서가 있는 tower를 사용합니다. rank는 정수와 유리수와 복소수의 층을 정하고, raise는 바로 다음 층으로만 이동하며, raise-to는 두 값이 더 높은 입력 층에 닿을 때까지 반복합니다. 정수와 유리수는 유리수 덧셈을 사용하고 유리수와 복소수는 한 번 더 올린 뒤 복소수 덧셈을 사용합니다. 관련 없는 polynomial 태그는 rank가 없으므로 no-common-type을 반환합니다. tower는 이 순서가 있는 타입의 모호성을 줄이지만 모든 데이터 타입이 하나의 계층에 들어간다거나 아래로 내리는 일이 항상 손실 없다는 뜻은 아닙니다.
(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)))- 출력
- —
- 값
- —
- 진단
- —
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이 필요하지 않아야 합니다.