'리스프'에 해당되는 글 2건

  1. 2010.02.09 taoi-part1-aux
  2. 2009.12.28 TAOI - Part 0 - session 2 (1)
2010.02.09 03:50

taoi-part1-aux

보조편

시간이 너무 잘 간다는 것이 가장 큰 문제이다. 일을 많이 하는 것도 없는데 시간은 무서운 속도로 지나간다. 그러나 이렇게 생각해 볼 수도 있다. TAOI 가 나오고 32년이 지났다. 그동안 무슨 일이 일어났는가? PC의 속도가 빨라졌고 인터넷이 흔해졌으며 초기의 해커들은 이제 늙어서 박물관에 들어가게 되었다. 

별로 주목을 받는 글은 아니겠지만 클래식 문서의 하나다. 어떤 사람들은 무엇인가를 발견했다고 한다. 

나도 많이 생각할 꺼리를 얻었지만 정작 얻은 것은 키보드로 생각하면서 부터다. 돌아가는 소스에 대해 손으로 생각하면서 많은 것을 생각한다, 그러니 손으로 생각하지 못한다면 만들 수 없는 거다. 이런 것을 일종의 수행이라고 해야 할 것인가? 만약 그렇다면 프로젝트를 스킴이나 리스프로 진행하면 아주 많은 것을 얻을 수 있을 것 같다. 책만 읽고 있다면 아무리 많은 시간을 투자해도 돌리고 만드는 것만큼 문제와 친해질 수는 없다. 책의 문제만 풀고 있어도 마찬가지다. 

TAOI 다음으로는 PAIP 가 기다리고 있다. (그전에 예비지식으로 Kowalski 와 Warren 의 글이 기다리고 있다.  결국 Prolog 를 만나게 되어있다. 프롤로그는 요즘말로 하면 나에게 OTL 을 선사한 적이 있는 언어다. 백트래킹 하다가 내 머리를 디버깅해야 했다.) PAIP는 조금 읽다가 접었던 적이 있는 , 나름 겁나는 책이다. On LISP 과는 다른 종류의 공포감을 주는 책이다. 

어쩌면 그 전에 LISP based Processors 로 컴파일러까지 한번 손으로 볼지 모른다. 그러려면 레지스터 머신을 SICP방식이건 아니면  나홀로 만드는 방식이건 또 제작을 해야 한다.   아니면 내친김에 람다 페이퍼를 모두 리뷰할지도 모른다. 나중의 기억을 위해서다. (망각대왕이기 때문이다.)
 
시간이 제일 큰 변수다.   이런 일들은 사실 2006년에도 다 해 치울 수 있던 문제인데 그때는 카메라와 자동차에 미쳐있었다. 아무튼 사람은 너무 많은 일을 다 한꺼번에 다 할 수는 없다.  오히려 하고 있는 일에 집중하는 편이 더 효율이 좋다.  

갑자기 너무 많은 생각을 했나보다. 급하지 않게 조금씩 나가는 법을 , 겸손한 법을 터득햇으면 지금보다는 훨씬 더 잘할 수 있을 것 같은데 시계를 보고 있으면 공포심이 든다. 이건 큰 병이다. 

-------------------------------

아무튼 상태(state)라는 주제를 진행하기 전에 한가지 문제를 해결해야 한다. 톱레벨 환경 구축의 문제다. 렉시컬 스코프가 돌아가지 않은 것은 톱레벨의 문제였다. 

아무튼 1장의 마지막 부분을 보자.

새로운 드라이버 루프 (그림10)은 더 이상  BlND 를 톱레벨 환경의 정의에서 사용하지 않는다. 대신 모든 톱레벨 정의는 환경의 한 프레임에 들어가도록 한다. 
새로운 정의가 만들어질 때  기존의 환경에서 예전에 정의된 이름들의 리스트와 값들의 리스트를 뽑아 새로운 톱레벨 환경을 만든다.  이때 이름들과 값들은  따로 보강(augmented)된다. &procedure 객체를 따로 만들지 않고 이 드라이버는  &LABELED객체 를 만든다. 형식은 같으나 환경은 갖고 있지 않다. &LABELED객체는 순전히 내부적인 것이라서 유저 프로그램에서는 보이지 않는다.  변수값찾기를 하다가 LOOKUP1 과 만나게 되면 바로   &procedure 객체를  생성하며 이때 사용되는 환경은 곧바로 사용가능한 환경이다. 그것은 결국 톱레벨 환경이다. 
( When encounters such an object as the value of available, it immediately creates the corresponding &PROCEDURE-object, using the environment at hand , which turns out to be the top-level environment. )


마지막의 조금 아리송한 부분은 당연히 디버거로 확인해야 한다. 

우선 그림 10에 나오는 소스 코드를 보자. 책에 있는 대로 타이핑하고 조금 고쳐놓은 모습이다. 앞서의 렉시컬 스코프 코드에 덧붙인 것이다. 


(define (lookup1 name vars vals env)
  
      (cond ((null? vars)
             (lookup name (cdr env) ))
            
            ((eq? name (car vars)) 
             (COND ((not (list? (CAR VALS))) VALS)
                   ((EQ? (CAAR VALS) '&labeled)(LIST '&procedure (CADAR VALS)                     (CADDAR VALS) ENV))
                   (#T VALS)))
             
(define (driver-loop-1 env form  )
  
  (cond ((not (list? form ))    
         (driver-loop env (display (mceval form env))))
        ((eq? (car form ) 'define)
         (driver-loop 
          (LIST (CONS (CONS (CAADR FORM) (CAAR ENV))
                      (CONS (LIST '&labeled 
                                     (CDADR FORM)
                                     (CADDR FORM))
                                     (CDAR ENV))))
          
                      (display (CAADR FORM))))
        
              
  
        
        (#t (driver-loop  env (display (mceval form env)))) 
        
     )
 )


이제 프로그램을 돌려보자. 
디버그 모드에서

(driver)

(define (factorial n)
  (cond  ((= n 1)       1)
      (#t  (* (factorial (- n 1)) n))))

이번에 고친 프로시저로 만들어 내는 것은 일단 &LABELED object다.
ENV 는 다음과 같이 변한다.

env => {{{factorial + - * = eq? cons car cdr null? #t #f} {&labeled {n} {cond {{= n 1} 1} {#t {* {factorial {- n 1}} n}}}} + - * = eq? cons car cdr null? #t #f}}

BIND 를 사용하지 않았으니 대략 코드가 어떻게 환경을 조작하는 가를 테스트해보자. 
동작중이 프로그램에 브레이크를 걸고 form 변수로 입력한 팩토리얼로 환경을 만드는 것을 재조립해 보자.  

먼저 define 은 당연히 car 로 잡아낸다. 

> (car '(define (factorial n)
  (cond  ((= n 1)       1)
      (#t  (* (factorial (- n 1)) n)))))
-->define


드라이버 루프는 define된 함수이름을 환경변수와 cons 하여 이름을 갱신한다.
-->(CONS (CAADR FORM) (CAAR ENV)))

우선 함수명 facorial 을 찾아야 한다. 

> (cadr '(define (factorial n)
  (cond  ((= n 1)       1)
      (#t  (* (factorial (- n 1)) n)))))

-->(factorial n)

다시 이 값의 car 을 구한다.

> (caadr '(define (factorial n)
    (cond  ((= n 1)       1)
      (#t  (* (factorial (- n 1)) n)))))
-->factorial

이제 찾았다!

그리고 환경변수의 이름을 갖고 있는 리스트를 찾아 와야 한다.

> (caar '(((+ - * = eq? cons car cdr null? #t #f ) + - * = eq? cons car cdr null? #t #f)) )
(+ - * = eq? cons car cdr null? #t #f)

이제 함수이름을 환경변수와 cons 하여 이름을 갱신한다.
-->(CONS (CAADR FORM) (CAAR ENV)))

그러면 define 된 함수 이름이 환경변수표에 들어간 것이다. 여기서는 factorial 이 들어간다. 

그 다음은 같은 전략으로 
 (CONS (LIST '&labeled 
                                     (CDADR FORM)
                                     (CADDR FORM))
                                     (CDAR ENV))))
로 &labeled  object를 만들어야 한다.

먼저  (CDADR FORM) 에 해당하는 것은 프로시저 인자 리스트다. 
> (cdadr '(define (factorial n)
  (cond  ((= n 1)       1)
      (#t  (* (factorial (- n 1)) n)))))
-->(n)

 (CADDR FORM))에 해당하는 것은 프로시저의 몸(body)다.

> (caddr '(define (factorial n)
  (cond  ((= n 1)       1)
      (#t  (* (factorial (- n 1)) n)))))

-->(cond ((= n 1) 1) (#t (* (factorial (- n 1)) n)))

그리고 (CDAR ENV) 에 해당하는 것은 환경변수의 값에 해당하는 부분이다.
> (cdar '(((+ - * = eq? cons car cdr null? #t #f ) + - * = eq? cons car cdr null? #t #f)) )
-->(+ - * = eq? cons car cdr null? #t #f)

이것은 환경변수를 car 한값
> (car '(((+ - * = eq? cons car cdr null? #t #f ) + - * = eq? cons car cdr null? #t #f)) )
-->((+ - * = eq? cons car cdr null? #t #f) + - * = eq? cons car cdr null? #t #f)
여기에 cdr 을 다시 적용한 것이다.


그러면 전략은 어느정도 눈에 보이게 된다. 환경변수 top level  부분을 갖는 &labeled object 를 갖게 된 것이다. 


그러면 이렇게 만든 새로운 환경을 디버그 해보자.


(driver)

다시 앞의 정의를 반복하고

(define (factorial n)
(cond  ((= n 1)       1)
(#t  (* (factorial (- n 1)) n))))

이번에는 팩토리얼 3을 구해보자.
(factorial 3)

결국 이 코드가 만들어 내는 결과물은 이것이다:

((&labeled (n) (cond ((= n 1) 1) (#t (* (factorial (- n 1)) n)))) + - * = eq? cons car cdr null? #t #f)

앞의 &procedure 의 경우와 별로 달라보이지 않는다.

이제 이 프로시저가 돌아가는 것을 살펴보자,
(factorial 3)
은 프로시저의 적용이니 eval은 우선 factorial 의 값을 찾을 것이다. 
앞에서의 경우라면 당연히 

((&labeled (n) (cond ((= n 1) 1) (#t (* (factorial (- n 1)) n)))) + - * = eq? cons car cdr null? #t #f) 
를 찾아줄 것이다. 

그런데 이번의 전략은 lookup1에서 &labeled 물체를 다시 한번 바꾸어주는 것이다. 

그러면 lookup1이 만들어주는 물체는 다음과 같다. &procedure 로 바꾸어 준다. 같이 달려온 환경을 보면 factorial 을 다시 찾을 때(recursion 같은 경우)에는  &labeled 물체를 다시 처리해야 함을 알 수 있다. 
지난번의 문제가 해결된 것이다. 

(&procedure (n) (cond ((= n 1) 1) (#t (* (factorial (- n 1)) n))) (((factorial + - * = eq? cons car cdr null? #t #f) (&labeled (n) (cond ((= n 1) 1) (#t (* (factorial (- n 1)) n)))) + - * = eq? cons car cdr null? #t 


그러면 거의 다된 것이다. 트레이스를 계속하면 
fun => &procedure
args => {3}

가 나오게 된다.
이론상으로는 문제가 없는 것 같은데 에러다. 
한참을 생각해보아도 문제를 알기는 어렵다, 몇 번 트레이스 , 다시 디버그하여  에러를 찾는다.

문제는 lookup1에서 나온 것 같다. lookup1의 결과를 처리하는 value1이 car 를 처리하면 리스트가 한 레벨 사라지고 만다. 
결국 이 부분을 해결하기 위해 value1 에 손을 대는 것은 그렇고 임시적인 방편이지만 lookup1 을 손보면 된다. 다행히 이 부분은 내부적이라 아무도 모른다.   


이 부분은 TAOI 의 저자들이 간과한 에러인 것 같다! 
일단 버그픽스를 해보자 .list를 두번 적용했다. 바깥쪽의 list 함수는 value1이 없애 버리는 한 레벨의 괄호에 대한 방어막인 셈이다,
다음은 버그픽스된 lookup이다. 아래와 같이 바꾸면 문제가 없어진다.
 
(define (lookup1 name vars vals env)
(display vals)
(cond ((null? vars)
(lookup name (cdr env) ))

((eq? name (car vars)) 
(COND ((not (list? (CAR VALS))) VALS)
((EQ? (CAAR VALS) '&labeled)  ( list(LIST '&procedure (CADAR VALS) (CADDAR VALS) ENV )) )
(#T VALS)))

그러면 이제 다시 팩토리얼 계산해 보자. 
이제 이런 것들을  생각하면서 손으로 생각해볼 차례다.
다음번에는 파트2로 넘어가서 이번의 코드를 몇번 더 써야 한다. 

바꾼 소스는 아래에 있다. 

(define (value1 name slot )
( cond ((eq? slot '&UNBOUND) (display 'errorunbound))
(else (car slot))
)
)


(define (bind vars vals base-env)
(if (= (length vars) (length vals))
(cons (cons  vars vals) base-env)
(if (< (length vars) (length vals))
(display 'error Too many arguments supplied)
(display 'error Too few arguments supplied ))))

(define (value name env)
(value1 name (lookup name env))
)

(define (value1 name slot )
( cond ((eq? slot '&UNBOUND) (display 'errorunbound))
(else (car slot))
)
)

(define (lookup name env)
(cond ((null? env ) '&UNBOUND)
(else (lookup1 name (caar env) (cdar env) env))))

(define (lookup1 name vars vals env)
(display vals)
(cond ((null? vars)
(lookup name (cdr env) ))

((eq? name (car vars)) 
(COND ((not (list? (CAR VALS))) VALS)
((EQ? (CAAR VALS) '&labeled)  ( list(LIST '&procedure (CADAR VALS) (CADDAR VALS) ENV )) )
(#T VALS)))


(else (lookup1 name (cdr vars) (cdr vals) env))))



(define (driver)
(driver-loop '(((+ - * = eq? cons car cdr null? #t #f ) + - * = eq? cons car cdr null? #t #f)) (display '|LITHP ITH LITHTENING|))
)

(define (driver-loop env hunoz)
(driver-loop-1 env (read)))

(define (driver-loop-1 env form  )

(cond ((not (list? form ))    
(driver-loop env (display (mceval form env))))
((eq? (car form ) 'define)
(driver-loop 
(LIST (CONS (CONS (CAADR FORM) (CAAR ENV))
(CONS (LIST '&labeled 
(CDADR FORM)
(CADDR FORM))
(CDAR ENV))))

(display (CAADR FORM))))




(#t (driver-loop  env (display (mceval form env)))) 

)
)


(define (mceval exp env )
(cond ((not (list? exp)) ;;atom exp
( cond
((number? exp ) exp)
(else  (value exp env))
))

((eq? (car exp) 'quote)  (cadr exp))
((eq? (car exp) 'cond) ( evcond (cdr exp)  env ))
((eq? (car exp) 'lambda) (list '&procedure (cadr exp) (caddr exp) env) )
(else
(mcapply (mceval (car exp)  env)
(evlis (cdr exp)  env )
) )))

(define (evcond clauses env  )

(cond ((null? clauses) 'error)
((mceval (caar clauses) env )
(mceval (cadar clauses) env ))
(else (evcond (cdr clauses) env  ))))

(define (mcapply fun args)
(display fun)

(cond ((primeop? fun) (primeop-apply  fun args))
((eq? (car fun) '&procedure) (mceval (caddr fun) (bind (cadr fun) args (cadddr fun)))) 
(else 'error-mcapply )  ))

(define (evlis  arglist env  )        ;map evaluator over list

(cond ((null? arglist) '())

(else (cons (mceval (car arglist) env )

(evlis (cdr arglist) env )))))

(define (primeop? fun)
(or (eq? fun '+)
(eq? fun '-)
(eq? fun '/)
(eq? fun '*)
(eq? fun '=)
(eq? fun 'eq?)
(eq? fun 'car)
(eq? fun 'cdr)
(eq? fun 'cons)
(eq? fun 'null?)

;; more
))

(define (primeop-apply fun args)
(cond ( (eq? fun '+ ) (+ (car args) (cadr args)))
( (eq? fun '- ) (- (car args) (cadr args)))
( (eq? fun '* ) (* (car args) (cadr args)))
( (eq? fun '/ ) (/ (car args) (cadr args)))
( (eq? fun '= ) (= (car args) (cadr args)))
( (eq? fun 'eq? ) (eq? (car args) (cadr args)))
( (eq? fun 'cons ) (cons (car args) (cadr args)))
( (eq? fun 'car ) (car (car args)))
( (eq? fun 'cdr ) (cdr (car args)))
( (eq? fun 'null?) (null? (car args)))
; ( (eq? fun 'atom ) (* (car args) (cadr args)))
; ( (eq? fun '* ) (* (car args) (cadr args)))
))


저작자 표시
신고
Trackback 0 Comment 0
2009.12.28 22:39

TAOI - Part 0 - session 2

(밤에 갑자기 일어나 키보드를 두들기기 시작했다. 1시간 정도 쓰고 이제 앰프를 만들러가야 한다. 아침에 작업할 일이 있어 공간을 비워야 한다.
나중에 간단한 리비전이 있을 것이지만  할 얘기는 다 한 셈이다,)

몇개의 bind , lookup 프로시저들을 살펴보자. 
변수를 값들과 결합하는 프로시저가 bind이다. 그리고 이들을 찾는 함수가 lookup이다. 

예전에 root of lisp을 이야기하면서 (http://toyfab.tistory.com/entry/lisp-인터프리터-이야기-1 ) 아주 간단한 리스프 인터프리터의 이야기를 적은적이 있다. 이글은 원래 ibm developer works에 싣기 위해 작성했던 두개의 글에 사족을 단 것이다.

여기서 pair 와 assoc 이 bind와 lookup의 원형이라고 볼 수 있다.  인용하면:

- (pair. x y)는 길이가 같은 두 리스트를 받아 이들로부터 차례로 리스트의 각 
원소를 취한 쌍의 리스트를 돌려준다.  
(defun pair. (x y) 
(cond ((and. (null. x) (null. y)) '()) 
((and. (not. (atom x)) (not. (atom y))) 
(cons (list (car x) (car y)) 
(pair. (cdr x) (cdr y))))))

복잡해 보이지만 실제의 동작은 간단하다.
 
(pair. '(x y z) '(a b c)) ==>((x a) (y b) (z c))
 
- (assoc. x y)은 아톰 x와 pair로 만든 리스트 y를 받아 쌍의 첫 원소가 x와 
동일한 리스트의 두 번째 원소를 돌려준다. 
(defun assoc. (x y) 
(cond ((eq (caar y) x) (cadar y)) 
('t (assoc. x (cdr y))))) 

(assoc. 'x '((x a) (y b))) ==>a 
(assoc. 'x '((x new) (x a) (y b))) ==>new 

위의 예제들은 리스프에서 돌려본 것이다. 

이런 리스트는 ( (x 3) ( y 4) )  와 같은 모양을 하고 있다. 이른바 alist라고 부르는 것으로  모양은 다음과 같다.  리스프 1.5까지도 
이 리스트의 구조는 별로 변하지 않았다. 

taoi의 그림에서는 이들을 설명하는 부분이 있다. 



그러나  이들의 스킴버전도 있다. 

여러가지 변형이 있지만 타이핑하는 수고를 덜기 위해서 예전에 sicp 강의에서 사용한 코드를 살펴보자 (책에 있는 코드가 아니다.):

(define bind

  (lambda (params values env) ;add a new frame

    (cons (cons 'frame

                (make-frame-body params values)) env))) 
(define make-frame-body

  (lambda (params values) ;frame body is an association  list
       (cond ((null? params)

           (cond ((null? values) '())
                 (else (error
                         '"Too many values supplied"))))
          ((null? values)

           (error '"Too few values supplied"))

          (else (cons (cons (car params) (car values))

                      (make-frame-body (cdr params)

                                       (cdr values)))))))

둘은 명백하게 환경을 만드는 프로시저들이다. 
스킴 인터프리터에서 이들을 돌려보자.  환경을 만드는 실험을 할 수 있다. 앞에 frame 이라고 하는 글자가 붙는 것을 제외하면 이들은
sicp 비디오렉처 7a의 첫 1/3  후반부의 bind와 같다. 

실제로 스킴에서 돌려보면 이들의 동작은 아주 뻔하다.  정의에서 lambda (params values env) 라고 되어있는 것처럼 
인자와 값을 읽어 env에 붙이는 것이다. 첫번째로 변수가 저장되는 공간 환경은 '()라고 볼 수 있다. 

(bind '(a) '(10) '())
==>((frame (a . 10)))

(bind '(a d) '(10 100) '())
==>((frame (a . 10) (d . 100)))

이 환경에  변수 b의 값을 정의해보자. 새로운 환경이라고 할 수 있다.

(bind '(b) '(10) '((frame (a . 10) (d . 100))))
==>((frame (b . 10)) (frame (a . 10) (d . 100)))

b의 값을 새로운 환경에서 재정의하면 

(bind '(b) '(20) '((frame (b . 10)) (frame (a . 10) (d . 100))))
==>((frame (b . 20)) (frame (b . 10)) (frame (a . 10) (d . 100)))

alist는 특별한 것이 아니므로 왼쪽으로부터 찾으면 b의 값을 20이 먼저 나올 것이다. 뒤의 10이라는 값을 찾지는 않는다. 
이런 작업을 몇백번 더해도 비슷하게 복잡한 리스트를 얻을 수 있을 것이다. 
이런 환경에서 값을 가져오는 작업이 lookup이다.   코드는 아주 단순하다. 

(define lookup
  (lambda (var env)
    (cond ((null? env)
           (error '"Unbound variable" var))                                 ;not in any frames
           (else ((lambda (binding)
                   (cond ((null? binding)                                     ;check each frame
                          (lookup var (cdr env)))                                     ;in turn
                         (else (cdr binding))))                                     ;(<varname>.<value>)

                 (find-binding var (cdr (car env)))))))) 

(define find-binding
  (lambda (var frame-body) ;this is just assq
    (cond ((null? frame-body) '())
          ((eq? var (car (car frame-body)))
           (car frame-body))
          (else (find-binding var (cdr frame-body))))))

lookup은  (lambda (var env) .. 로 환경에서 변수를 찾는다.
(이 부분 역시 비디오 강의 sicp 7a의 앞 1/3의 뒷부분에 설명하는 부부과 거의 유사하다. 실제로 find-binding은 서스만의 강의에서는 assq 라는 함수로 되어있다. 동작은 같다.   동작은 전형적인 sicp 방식이다. )


머리보다 손으로 생각하는 편이 빠르니 앞에서 만든 환경에서 a를 찾아보자.  찾기는 리스트의 앞부분부터 시작되므로 10을 찾고 더 이상 찾지 않는다. ( 여러개를 찾아 어느것을 사용할까 고민하는 전략이 아니다.)

( lookup 'a '((frame (b . 20)) (frame (b . 10)) (frame (a . 10) (d . 100))) )
==>10


이런 작업은 실제 인터프리터코드에서 다음과 같이 일어난다:

;;; a meta-circular evaluator which can evaluate itself. 
(define mc-eval

  (lambda (exp env)
    (cond ((number? exp) exp)              ;base-case
          ((symbol? exp) (lookup exp env)) ;base case
          ((eq? (car exp) 'quote)
           (car (cdr exp))) ;special forms
          ((eq? (car exp) 'cond)
           (evcond (cdr exp) env))
          ((eq? (car exp) 'begin)
           (evseq (cdr exp) env))
          ((eq? (car exp) 'lambda)
           (list 'proc (cdr exp) env))
          ((eq? (car exp) 'define)
           (evdefine (cdr exp) env))
          (else (mc-apply (mc-eval (car exp) env)

                          (evlist (cdr exp) env))))))

 
여기서 식 exp가 변수인 경우는 우리가 수동으로 만들었던 환경에서 돌려본 것과 같이 단순히 lookup을 사용한다:
  ((symbol? exp) (lookup exp env)) 
실제로 exp가 a 였다면 앞의 실행예처럼 10을 돌려주었을 것이다. 


기왕 여기까지 돌려 보았으니  실제로 bind를 사용하는 예를보자 . 1차적으로 bind를 사용하는 경우는 함수의 적용(apply)의 경우다. 
아직 코드를 다 이해할 필요는 없다. 
만약 코드를 모두 이해했다면 지금쓰고 있는 글을 읽을 필요가 없다. 
중요한 것은 함수의 적용에서 bind를 사용해서 새로운 환경을 만들어 낸다는 사실이다.   (사실은 이부분이 함수형언어의 정수다!)

 (bind (car (car (cdr fun)))                                        ;formal params
                          args                                                         ;supplied args
                          (car (cdr (cdr fun))))))                                       ;saved env 

코드를 이해하지 않더라도 무언가 환경을 새로 만들어 내고 있다는 것을 알 수 있다. apply의 전체 코드는 다음과 같다.  ( 거듭 말하지만 다 이해할 필요는 없다.)  :
  
(define mc-apply

  (lambda (fun args)

    (cond ((not (pair? fun))

           (apply fun args))   ;ground out 
          ((eq? (car fun) 'proc)

           (mc-eval (car (cdr (car (cdr fun))))

                                       ;procedure body

                    (bind (car (car (cdr fun)))                                        ;formal params
                          args                                                         ;supplied args
                          (car (cdr (cdr fun))))))                                       ;saved env 

          (else (error '"Unknown function")))))


그럼 이제 taoi에서 사용할 lookup과 bind의 구조를 살펴보자. 앞서의 예제와 비슷하다. 우선 taoi문서의 그림 3을 보자. 



 
책에서는 이들이 alist 가 아니라고 말하고 있다.  그 이유로 다음과 같은 그림을 보여주고 있다. alist보다는 조금 효율적이라는 것이다. 



그러나 alist이건 alist가 아니건 이들이 인터프리터의 동작에는 영향을 크게 미치지 않는다. 효율에는 영향을 미칠지 몰라도 본질적인 부분은 변하지 않는다. 이 본질적인 부분을 설명하는 것이 taoi의 목적이다.  그래서 lookup과 bind 를   어떤 것을 사용하더라도 별다른 문제가 없다.  본질적인 문제는 bind로 환경을 만드는  방법이다. 처음에 서스만이 집요하게 알골 60을 열심히 공부하던 시작점이기도 하다.  리스프가 만들어지고 10여년이 지날때까지 렉시컬 스코프 또는 스태틱바인딩을 사용했다. 그런데 리스프와 거의 비슷한 시기에 출발한 알골은 처음부터 렉시컬스코프를 채택했다. 

다음번에는 가장 간단한 인터프리터 그러니까 데이타는 자유변수가 없고 프로시저만 자유변수인 인터프리터를 생각해 보겠다. TAOI 의 시작부분에 나오는 인터프리터다. 




저작자 표시
신고
Trackback 0 Comment 1


티스토리 툴바