지난 포스트에서 다룬 복소수 산술 연산과 더 이전에 다룬 유리수 산술 연산, lisp에 존재하는 기본 산술 연산을 활용해 일반화된 산술 연산을 설계하여 보자.
우선 일반화된 산술 프로시저를 다음과 같이 정의하자.
(define (add x y) (apply-generic 'add x y))
(define (sub x y) (apply-generic 'sub x y))
(define (mul x y) (apply-generic 'mul x y))
(define (div x y) (apply-generic 'div x y))
그리고 타입/연산 테이블에 기본 산술 연산 package부터 집어넣자.
(define (install-scheme-number-package)
(define (tag x) (attach-tag 'scheme-number x))
(put 'add '(scheme-number scheme-number)
(lambda (x y) (tag (+ x y))))
(put 'sub '(scheme-number scheme-number)
(lambda (x y) (tag (- x y))))
(put 'mul '(scheme-number scheme-number)
(lambda (x y) (tag (* x y))))
(put 'div '(scheme-number scheme-number)
(lambda (x y) (tag (/ x y))))
(put 'make 'scheme-number
(lambda (x) (tag x)))
'done)
그리고 전역에 scheme-number 타입 수의 생성자를 정의한다.
(define (make-scheme-number n)
((get 'make 'scheme-number) n))
이렇게 기존의 코드를 수정하지 않고도 쉽게 새로운 함수를 추가할 수 있다.
그리고 우리가 유리수_포스트 에서 작성했던 코드를 그대로 package로 추가할 수 있다.
(define (install-rational-package)
; 로컬 프로시저
(define (make-rat x y)
(let ((gcd-val (gcd x y)))
(if (> 0 (* x y))
(cons (- 0 (abs (/ x gcd-val))) (abs (/ y gcd-val)))
(cons (abs (/ x gcd-val)) (abs (/ y gcd-val))))))
(define (numer x) (car x))
(define (denom x) (cdr x))
(define (add-rat x y)
(make-rat (+ (* (numer x) (denom y)) (* (numer y) (denom x)))
(* (denom x) (denom y))))
(define (sub-rat x y)
(make-rat (- (* (numer x) (denom y)) (* (numer y) (denom x)))
(* (denom x) (denom y))))
(define (mul-rat x y)
(make-rat (* (numer x) (numer y))
(* (denom x) (denom y))))
(define (div-rat x y)
(make-rat (* (numer x) (denom y))
(* (denom x) (numer y))))
; 인터페이스 표에 put
(define (tag x) (attach-tag 'rational x))
(put 'add '(rational rational)
(lambda (x y) (tag (add-rat x y))))
(put 'sub '(rational rational)
(lambda (x y) (tag (sub-rat x y))))
(put 'mul '(rational rational)
(lambda (x y) (tag (mul-rat x y))))
(put 'div '(rational rational)
(lambda (x y) (tag (div-rat x y))))
(put 'make 'rational
(lambda (n d) (tag (make-rat n d))))
'done)
; 전역에 유리수 데이터 생성자 추가
(define (make-rational n d)
((get 'make 'rational) n d))
마지막으로 복소수 package를 정의하자. 이때, 지난_포스트에서 정의한 내용들은 이미 적용되어 있다고 생각한다.
(define (install-complex-package)
; 직각 좌표/극 좌표 package에서 가져온 constructor 프로시저
(define (make-from-real-imag x y)
((get 'make-from-real-imag 'rectangular) x y))
(define (make-from-mag-ang r a)
((get 'make-from-mag-ang 'polar) r a))
; 로컬 프로시저
(define (add-complex z1 z2)
(make-from-real-imag (+ (real-part z1) (real-part z2))
(+ (imag-part z1) (imag-part z2))))
(define (sub-complex z1 z2)
(make-from-real-imag (- (real-part z1) (real-part z2))
(- (imag-part z1) (imag-part z2))))
(define (mul-complex z1 z2)
(make-from-mag-ang (* (real-part z1) (real-part z2))
(+ (imag-part z1) (imag-part z2))))
(define (div-complex z1 z2)
(make-from-mag-ang (/ (real-part z1) (real-part z2))
(- (imag-part z1) (imag-part z2))))
; 표에 인터페이스 put
(define (tag x) (attach-tag 'complex x))
(put 'add '(complex complex)
(lambda (x y) (tag (add-comlex x y))))
(put 'sub '(complex complex)
(lambda (x y) (tag (sub-comlex x y))))
(put 'mul '(complex complex)
(lambda (x y) (tag (mul-comlex x y))))
(put 'div '(complex complex)
(lambda (x y) (tag (div-comlex x y))))
(put 'make-from-real-imag 'complex
(lambda (x y) (tag (make-from-real-imag x y))))
(put 'make-from-mag-ang 'complex
(lambda (r a) (tag (make-from-mag-ang r a))))
'done)
; 전역에 복소수 생성자 두개 추가
(define (make-complex-from-real-imag x y)
((get 'make-from-real-imag 'complex) x y))
(define (make-complex-from-mag-ang r a)
((get 'make-from-mag-ang 'complex) r a))
복소수 데이터의 경우, tag가 두개가 붙어 있게 된다. 먼저 complex 태그가 붙어있고, 그 태그를 떼어내면 더 아래 레벨에 직각/극 좌표 방식을 구분해주는 태그가 있다.
연습문제 2.77
샤실 위의 복소수 코드는 아직 불완전하다. (magnitude z)를 실행해 보면 magnitude / complex 에 맞는 연산이 표에 없다는 에러가 발생한다. 실제로 우리는 complex에 쓰일 selector 연산을 표에 집어넣은 적이 없다. 아래 코드를 complex package에 집어넣으면 에러가 해결된다.
(put 'real-part '(complex) real-part)
(put 'imag-part '(complex) imag-part)
(put 'magnitude '(complex) magnitude)
(put 'angle '(complex) angle)