2015년 1월 26일 월요일

컴퓨터 프로그램의 구조와 해석(1) 2장 3

2.4  요약된 데이터의 표현방식이 여러 가지일 때

데이터 요약)
한 프로그램을 설계할 때 그 프로그램에서 다루고자 하는 데이터를 어떻게 구현했는지가 외부에 드러나지 않도록 프로그램의 체제를 잡아나가는 방법

=> 요약의 경계를 세워야 한다.

장점: 큰 프로그램의 설계를 작은 일로 나누어 다룰 수 있다.
단점: 데이터의 표현 방식이 무엇인지 알기 힘들 때가 있다.
          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) 프로시저가 필요한다.
장점: 상속의 개념을 쉽게 구현할 수 있다.
문제점: 대개는 쉽게 구현할 수 없다.

//대개의 경우 데이터 타입은 위와 같이 구성되는 데 이런 구성에서 '끌어올리는' 방법과 
'끌어내리는' 방법이 여러 가지로 갈리기 때문에 일반화시키기 쉽지않다.


2.5.3 연습: 기호 식 대수

다항식 산술 연산

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))
앞에서 쓰이는 작은 모듈들
한 번만 훑어보면 쓰임새를 알 수 있다.
*/


댓글 없음:

댓글 쓰기