데이터 요약)
한 프로그램을 설계할 때 그 프로그램에서 다루고자 하는 데이터를 어떻게 구현했는지가 외부에 드러나지 않도록 프로그램의 체제를 잡아나가는 방법
=> 요약의 경계를 세워야 한다.
장점: 큰 프로그램의 설계를 작은 일로 나누어 다룰 수 있다.
단점: 데이터의 표현 방식이 무엇인지 알기 힘들 때가 있다.
ex)
복소수를 나타내는 두가지 방식이 있다
1) 직각 좌표계
2) 극 좌표계
두 방식에는 각각 장단점이 있다, 그러므로 두 표현을 모두 쓸 수 있는 프로시저를 만들어야 한다.
'덧붙여'넣을 수 있는 방식이 필요한 이유)
프로그래밍 시스템을 설계한다는 것이 여러 사람이 오랜 시간에 걸쳐 함께 해야 하는 일이라는 데 있다. 게다가 시간이 흘러가면서 요구사항도 자꾸 달라지기 때문에, 그만큼 일하는 기간도 늘어지게 마련이다.
따라서 이런 경우에는 한 프로그램 속에 서로 다른 설계방식이 함께 자리잡을 수 있도록 해야 한다.
일반화된 프로시저)
한 데이터를 표현하는 방식이 하나가 아닐 때 이런 데이터들을 받아들여 정해진 연산을 해낼 수 있도록 정의된 프로시저
2.4.1 복소수 표현
(make-from-real-imag (real-part z) (imag-part z))
//실수부와 허수부로 이루어진 복소수를 내놓는 프로시저
(make-from-mag-ang (magnitude z) (angle z))
//크기와 각도를 받아서 복소수를 만들어 내는 프로시저
(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 (* (magnitude z1) (magnitude z2))
(+ (angle z1) (angle z2))))
(define (div-complex z1 z2)
(make-from-mag-ang (/ (magnitude z1) (magnitude z2))
(- (angle z1) (angle z2))))
삼각함수 공식에 따라 직각 좌표 표현과 극좌표 표현 방식으로 변환할 수 있는 프로시저를 만든다.(생략)
2.4.2 타입을 표시한 데이터
2.4.1에서 했던 직각 좌표 표현과 극좌표 표현을 모두 받아들일 수 있는 시스템을 설계하는것이 가능하다. 이러한 시스템을 만들때 가장 선행되어야 하는 일은 위의 두 표현방식을 구별할 수 있어야 한다.
해결책 => '타입을 표시'하는 기법
ex)
한 데이터 물체에서 그 표시와 실제 데이터를 뽑아내는 프로시저(type-tag와 contents)
(define (attach-tag type-tag contents)
(cons (type-tag contents))
(define (type-tag datum) // 타입 정의
(if (pair? datum)
(car datum)
(error "Bad tagged datum -- TYPE-TAG" datum)))
(define (contents datum) //실제 데이터 정의
(if (pair? datum)
(cdr datum)
(error "Bad tagged datum -- CONTENTS" datum)))
(define (rectangular? z)
(eq? (type-tag z) 'rectangular))
(define (polar? z)
(eq? (type-tag z) 'polar))
=> 위 식들과 삼각함수 공식 프로시저로 두 표현방식을 오간다.
// 데이터마다 타입을 붙임으로서 실제 연산에 어떠한 데이터가 들어와도 들어간다.
<= 인자로 받은 데이터 타입에 맞게 연산하도록 정의되있음으로
2.4.3 데이터 중심 프로그래밍과 덧붙임 성질
타입에 따라 나누어 맡기기)
데이터 타입을 따져보고 그에 알맞은 프로시저를 불러 쓰는 방법
-약점)
1. 일반화된 인터페이스 프로시저를 만들고자 할 때 모든 표현 방식을 미리 알아야 한다.
2. 따로 설계된 데이터 표현을 뭉쳐 한 시스템으로 엮어 내는 데, 서로 다른 표현 속에서 똑같이 이름 지은 프로시저가 없어야 한다.
==> 해결책
데이터 중심 프로그래밍
일반화된 연산(= 서로 다른 데이터 타입 사이에 공통된 연산)
== 연산과 타입을 두 축으로 하는 이차원 표를 다루는 것
//각각 표의 원소 하나에 대응되는 프로시저가 들어있다.
데이터 타입에 따라 할 일을 나눠 맡기는 프로시저
(put <op> <type> <item>)
<op>와 <type>으로 <item>을 꺼내 쓸 수 있도록 <item>을 표에 집어넣는다.
(get <op> <type>)
표에서 <op>,<type> 자리에 있는 원소를 꺼내온다. 그런 원소가 표에 없으면 거짓이라고 답한다.
복소수 시스템을 만드는 데 데이터 중심 프로그래밍 기법
ex)
(define (install-rectangular-package)
//갇힌 프로시저
// 이렇게 갇힌 프로시저로 선언해놓으면 다른 프로시저에서 이름이 겹칠 일이 없다.
(define (real-part z) (car z))
(define (imag-part z) (cdr z))
(define (make-from-real-imag x y) (cons x y))
(define (magnitude z)
(sqrt (+ (square (real-part z))
(square (imag-part z)))))
(define (angle z)
(atan (imag-part z) (real-part z)))
(define (make-from-mag-ang r a)
(cons (* r (cos a)) (* r (sin a))))
// 인터페이스: 표에 있는 연산을 사용하기 위해 먼저 정의해놓은 부분
(define (tag x) (attach-tag 'rectangular x))
(put 'real-part '(rectangular) real-part)
(put 'imag-part '(rectangular) imag-part)
(put 'magnitude '(rectangular) magnitude)
(put 'angle '(rectangular) angle)
(put 'make-from-real-imag 'rectangular
(lambda (x y) (tag (make-from-real-imag x y))))
(put 'make-from-mag-ang 'rectangular
(lambda (r a) (tag (make-from-mag-ang r a))))
'done)
(define (apply-generic op . args)
(let ((type-tags (map type-tag args)))
(let ((proc (get op type-tags)))
(if proc
(apply proc (map contents args))
(error
"No method for these types -- APPLY-GENERIC"
(list op type-tags))))))
//표에서 알맞은 프로시저를 꺼내는 프로시저
2.5 일반화된 연산 시스템
이 장에서는 한 데이터의 표현이 서로 다를 때는 물론이고, 아예 인자마다 종류가 다른 경우까지 다룬다.
2.5.1 일반화된 산술 연산
ex)
위에 표에서 기본 수가 들어오면 기본 + 연산처럼, 유리수가 들어오면 add-rat처럼, 복소수가 들어오면 add-complex처럼 돌아가는 연산을 만드는 것
(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))
(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)
// 정수 산술 연산
(define (install-rational-package)
(define (numer x) (car x))
(define (denom x) (cdr x))
(define (make-rat n d)
(let ((g (gcd n d)))
(cons (/ n g) (/ d g))))
(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) (denom y))
(* (denom x) (denom y))))
(define (add-rat x y)
(make-rat (* (numer y) (denom x)))
(* (denom x) (denom y))))
//인터페이스
(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))
//유리수 산술 연산
(define (install-complex-package)
(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-real-imag (* (magnitude z1) (magnitude z2))
(+ (angle z1) (angle z2))))
(define (div-complex z1 z2)
(make-from-real-imag (/ (magnitude z1) (magnitude z2))
(- (angle z1) (angle z2))))
//인터페이스
(define (tag z) (attach-tag 'complex z))
(put 'add '(complex complex)
(lambda (z1 z2) (tag (add-complex z1 z2))))
(put 'sub '(complex complex)
(lambda (z1 z2) (tag (sub-complex z1 z2))))
(put 'mul '(complex complex)
(lambda (z1 z2) (tag (mul-complex z1 z2))))
(put 'div '(complex complex)
(lambda (z1 z2) (tag (div-complex z1 z2))))
(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)
//복소수 산술 연산
//사실상 각각 다른 프로시저에 갇혀있기 때문에 같은 이름을 써도 된다.
2.5.2 타입이 다른 데이터를 엮어 쓰는 방법
ex) 복소수와 정수를 더하는 연산
'섞어붙이기'
1. 서로 다른 타입에서 일어날 수 있는 연산마다 그에 해당하는 프로시저를 만든다.
2. 타입 바꾸기
ex) 정수와 복소수를 산술관계로 엮어 써야 할 때, 정수를 허수부가 0인 복소수로 보면,
두 복소수를 엮어 쓰는 문제로 풀면 된다.
(define (scheme-number->complex n)
(make-complex-from-real-imag (contents n) 0))
(define (apply-generic op . args)
(let ((type-tags (map type-tag args)))
(let ((proc (get op type-tags)))
(if proc
(apply proc (map contents args))
((if (= (length args) 2) //인자의 타입이 두개면
(let ((type1 (car type-tags))
(type2 (cadr type-tags))
(a1 (car args))
(a2 (cadr args)))
(let ((t1->t2 (get-coercion type1 type2))
(t2->t1 (get-coercion type2 type1)))
(cond (t1->t2
(apply-generic op (t1->t2 a1) a2))
(t2->t1
(apply-generic op a1 (t2->t1 a2)))
(else
(error "No method for these types"
(list op type-tags))))))
(error "No method for these types"
(list op type-tags)))))))
타입의 계층 관계
계층구조)
계층관계를 끌어올리고(raise) 끌어내리는(lower) 프로시저가 필요한다.
장점: 상속의 개념을 쉽게 구현할 수 있다.
문제점: 대개는 쉽게 구현할 수 없다.
//대개의 경우 데이터 타입은 위와 같이 구성되는 데 이런 구성에서 '끌어올리는' 방법과
'끌어내리는' 방법이 여러 가지로 갈리기 때문에 일반화시키기 쉽지않다.
다항식 산술 연산
1. 다항식이 무엇인지 정한다.
// 예를 들어 변수가 하나인 방정식이 있을 때 무엇을 변수로 설정하는 지에 따라 식이 전혀 다르게 변한다.
ex)
poly: 변수 하나와 여러 마디로 이루어진 데이터 구조
다항식에서 요소를 골라낼 때는 variable과 term-list 프로시저 사용
변수와 마디 리스트를 다항식을 만들 때에는 make-poly 프로시저 사용
(define (add-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(add-terms (term-list p1)
(term-list p2)))
(error "Polys not in same var -- ADD-POLY"
(list p1 p2))))
(define (mul-poly p1 p2)
(if (same-variable? (variable p1) (variable p2))
(make-poly (variable p1)
(mul-terms (term-list p1)
(term-list p2)))
(error "Polys not in same var -- MUL-POLY"
(list p1 p2))))
(define (install-polynomial-package)
(define (make-poly variable term-list)
(cons variable term-list))
(define (variable p) (car p))
(define (term-list p) (cdr p))
(define (add-poly p1 p2) ...)
(define (mul-poly p1 p2) ...)
(define (tag p) (attach-tag 'polynomial p))
(put 'add '(polynomial polynomial)
(lambda (p1 p2) (tag (add-poly p1 p2))))
(put 'mul '(polynomial polynomial)
(lambda (p1 p2) (tag (mul-poly p1 p2))))
(put 'make 'polynomial
(lambda (var terms) (tag (make-poly var terms))))
'done)
//poly를 표현하는 프로시저
(define (add-terms L1 L2)
(cond ((empty-termlist? L1) L2) //마디리스트가 비었는가?
((empty-termlist? L2) L2)
(else
(let ((t1 (first-term L1)) (t2 (first-term L2)))
//마디리스트에서 차수가 가장 높은 마디
(cond ((> (order t1) (order t2))
(adjoin-term
t1 (add-terms (rest-terms L1) L2)))
//나머지 마디
((< (order t1) (order t2))
(adjoin-term
t2 (add-terms L1 (rest-terms L2))))
(else
(adjoin-term
(make-term (order t1)
(add (coeff t1) (coeff t2)))
(add-terms (rest-terms L1)
(rest-terms L2)))))))))
//두 다항식의 마디 리스트를 더하는 프로시저
*마디와 마디의 계수를 더할 때, 일반화된 덧셈 프로시저인 add를 쓴다.
(define (mul-terms L1 L2)
(if (empty-termlist? L1)
(the-empty-termlist)
(add-terms (mul-term-by-all-terms (first-term L1) L2)
(mul-terms (rest-terms L1) L2))))
(define (mul-term-by-all-terms t1 L)
(if (empty-termlist? L)
(the-empty-termlist)
(let ((t2 (first-term L)))
(adjoin-term
(make-term (+ (order t1) (order t2))
(mul (coeff t1) (coeff t2)))
(mul-term-by-all-terms t1 (rest-terms L))))))
//마디 하나와 마디 리스트를 인자로 받아서 그 마디를 마디 리스트의 모든 마디에 하나하나 곱하는 프로시저
마디 리스트 표현하기
// 높은 차수에서 낮은 차수까지 차례대로 마디 리스트를 쓰기 때문에 차례 매긴 리스트 사용
빽빽함)
한 다항식에서 차수마다 계수가 0이 아닌 마디가 대부분이라면 이런 다항식은 빽빽하다.
ex)
A: x^5 + 2*x^4 + 3*x^2 - 2*x - 5
//리스트 표현 (1 2 0 3 -2 -5) <= 빽빽하다
B: x^100 + 2*x^2 + 1
//리스트 표현 ((100 1) (2 2) (0 1)) <= 성기다
//성긴 다항식에서 빽빽한 다항식의 리스트 표현을 이용하면 매우 비효율적이다.
(define (adjoin-term term term-list)
(if (=zero? (coeff term))
term-list
(cons term term-list)))
/*
(define (the-empty-termlist) '())
(define (first-term term-list) (car term-list))
(define (rest-terms term-list) (cdr term-list))
(define (empty-termlist? term-list) (null? term-list))
(define (make-term order coeff) (list order coeff))
(define (order term) (car term))
(define (coeff term) (cadr term))
앞에서 쓰이는 작은 모듈들
한 번만 훑어보면 쓰임새를 알 수 있다.
*/
댓글 없음:
댓글 쓰기