2009.03.26 22:03

변수에 대해 생각해보기 (2) Teach Yourself Scheme in Fixnum Days

오늘은 지난번의 동적 바인딩 생각해보기 (1) 에 대해 변수에 대해 생각해보기 (2)라는 제목으로 적어 봐야 할 것 같다.
오늘은 let 에 대한 부분을 정리하기 위해 스킴의 문헌 Teach Yourself Scheme in Fixnum Days 의 5장을 인용한다.

스킴의 교과서로 출판된 것은 아니지만 일정한 날자안에 스킴을 배울 수 있게 해준다는 책이다.
일단 책으로 여겨야 할 것 같다.





Teach Yourself Scheme in Fixnum Days 라는 제목으로 보아서는 킬러 앱스인데 사실 그렇게 많이 보지는 않는다. 스킴의 사용자들이 한정되어 있기 때문이다.  그리고 21 days가 아니고 fixnum days라고 적은 것은 들여다보고 있으면 언젠가는  이해할 수 있을 것이라는 사실을 알기 때문이라고 생각한다.  언제인가는..
아무튼 책들을 많이 펼치지 않고 스킴의 많은 부분을 요약할 수 있다. 그 이유는 이 글(책)이 r5rs의 요약본이나 마찬가지기 때문이다.  

결론은 매우 잘 쓴 책이며 간결하게 정리되어 있다. 예제들도 적당하다. 분명히 도움이 되는 사람들도 있을 것이다. 가끔 비교 검토할 가치는 충분하다.

SICP의 무서움은 이런 절차를 무시하고 바로 총을 주고 전쟁터로 보내는 방식이다. 간결하지도 않으며 예제들도 어렵다. 그리고 중요한 차이점들은 연습문제로 남겨 놓는다. 설명이 연습문제에 있는 경우도 있으며 전혀 친절하지도 않다.
(하지만 둘 다 좋은 책이다. 가르치는 방법도 여러가지이며 배우는 방법도 여러가지이고 대가 학생은 선생을 찾아 다닌다. )

http://www.ccs.neu.edu/home/dorai/t-y-scheme/t-y-scheme.html



5

Lexical variables

스킴의 변수들은 렉스컬 스코프를 따른다.  아래의 예제들은 람다 인자를 사용한 지역변수들이다. 이들은 프로시저 호출에서 바인딩을 일으키며 그 범위는 프로시저의 몸이다.

(define x 9)
(define add2 (lambda (x) (+ x 2)))

x        =>  9

(add2 3) =>  5
(add2 x) =>  11

x        =>  9

글로벌 x가 있고 로컬인 x 가 있다.  그리고 x 라는 변수를 받는 add2 가 있다.  먼저 글로벌 x 는 9 이다.  그 다음 add2에 2를 입력하면 5가 나오고 x를 입력하면 9에 2를 더한  11 이 나온다.  로컬의 x 와 글로벌x는 다르기 때문에 글로벌 값은 영향받지 않고 9가 나온다. 당연해 보인다.  

그다음의 예제 set! 을 톱레벨에서 실행하면 x의 값을 20을 바꾼다.
 
(set! x 20)

글로벌 변수 x 는 이제 9에서  20이 되었다.

만약  set! 이 add2의 몸체 안에서 작용하면 어떻게 될까?

(define add2
  (lambda (x) (set! x (+ x 2))
    x))

set! 은 에제 로컬 변수에 2를 더하고 그 값을 되돌린다.  효과면에서 새로운  add2는 변한 것이 없다.

이제  add2 에 글로벌 x를 적용하자.

(add2 x) =>  22

add2안에서 set! 이 작용한 것은 로컬변수이다. (x는 프로시저를 만들면서 같이 만들어진 로컬 변수다.)

그래서 글로벌 x는 변하지 않는다.

x
=>  20

여기까지도 뻔한 내용이다.

아래는 로컬 x 가 그로벌 변수를 샤도우 했다는 내용이다. 이  내용은 sicp 3장의 시작부에 잘 나타난다.

Note that we had all this discussion because we used the same identifier for a local variable and a global variable. In any text, an identifier named x refers to the lexically closest variable named x. This will shadow any outer or global x's. Eg, in add2, the parameter x shadows the global x.

그러나 로컬 변수가 아니면 이야기는 달라진다.

A procedure's body can access and modify variables in its surrounding scope provided the procedure's parameters don't shadow them. This can give some interesting programs. Eg,

(define counter 0)

(define bump-counter
  (lambda ()
    (set! counter (+ counter 1))
    counter))

The procedure bump-counter is a zero-argument procedure (also called a thunk). It introduces no local variables, and thus cannot shadow anything. Each time it is called, it modifies the global variable counter -- it increments it by 1 -- and returns its current value. Here are some successive calls to bump-counter:

(bump-counter) =>  1
(bump-counter) =>  2
(bump-counter) =>  3
여기서는 로컬 변수가 없고 counter는 글로벌 변수를 참조한다.  (이런 용법을 free 변수라고 하며 
bump-cpunter는 글로벌이자 free변수인 counter를 증가시킨다. free변수는 람다에 의해 바인딩되지 
변수다.  프리변수는 많은 언어에서 적법하지 않은 요소지만 리스프에서는 예전부터 사용한 변수다.  
나중에 funarg를 정리하면서 반드시 생각해 보아야 할 숙제이기도 하다. )
 

5.1  let and let*


아래의 내용은 특별한 것이 없다. let과 let*의 차이점이다.  let* 이 사실상 let (let ( 처럼 let 안의 let  형태라는 것은 sicp의 연습문제에도 나온다.  sicp를 보지 않아도 아래의 예제 정도면 충분하다.

Local variables can be introduced without explicitly creating a procedure. The special form let introduces a list of local variables for use within its body:

(let ((x 1)
      (y 2)
      (z 3))
  (list x y z))
=>  (1 2 3)

As with lambda, within the let-body, the local x (bound to 1) shadows the global x (which is bound to 20).

The local variable initializations -- x to 1; y to 2; z to 3 -- are not considered part of the let body. Therefore, a reference to x in the initialization will refer to the global, not the local x:

(let ((x 1)
      (y x))
  (+ x y))
=>  21

This is because x is bound to 1, and y is bound to the global x, which is 20.

Sometimes, it is convenient to have let's list of lexical variables be introduced in sequence, so that the initialization of a later variable occurs in the lexical scope of earlier variables. The form let* does this:

(let* ((x 1)
       (y x))
  (+ x y))
=>  2

The x in y's initialization refers to the x just above. The example is entirely equivalent to -- and is in fact intended to be a convenient abbreviation for -- the following program with nested lets:

(let ((x 1))
  (let ((y x))
    (+ x y)))
=>  2

The values bound to lexical variables can be procedures:

(let ((cons (lambda (x y) (+ x y))))
  (cons 1 2))
=>  3

Inside this let body, the lexical variable cons adds its arguments. Outside, cons continues to create dotted pairs.

5.2  fluid-let


그런데 렉시컬 변수도 때로는 일정한 값으로 유지되는 편이 나을지도 모른다.  fluid -let 은 이런 경우에 사용된다.
fluid 라는 이름은 지난번에 설명한 다이나믹 바인딩과 유사한 성격이다.
fluid-let 은 글로벌 변수를 가리는 (샤도우하는 ) 것이 아니라 counter를 임시로 99로 세팅한다.

(fluid-let ((counter 99))
  (display (bump-counter)) (newline)
  (display (bump-counter)) (newline)
  (display (bump-counter)) (newline))

100 
101 
102 

 fluid-let 을 사용한 식의 계산이 끝나면 global counter의 값은 다시 원래로 돌아간다.

counter
=>  3

fluid-let 은 let과 다르다. let처럼 완전히 새로운  렉시컬 변수를 만드는 것이 아니라 현재의 렉시컬 변수의 바인딩만 바꾼다.

이제  다시 원점으로 돌아와서 let을 사용한 예제를 돌려보자.

(let ((counter 99))
  (display (bump-counter)) (newline)
  (display (bump-counter)) (newline)
  (display (bump-counter)) (newline))

결과는

4

5
6

글로벌 변수  counter는 아무런 영향을 받지 않았다. bump-counter를 호출하기는 했으나 bump-counter의 counter는 글로벌 변수를 참조했으며 아무런 영향이 없었던 것이다. 그러나 fluid-let은 일시적으로 모든 counter 변수의 바인딩을 유지했다.

(이것은 지난번의 dynamic 변수 예제를 보고 다시 한번 대조해 보면 이해할 수 있을 것이다.)
 

Ie, the global, which is initially 3, is updated by each call to bump-counter. The new lexical variable counter, with its initialization of 99, has no impact on the calls to bump-counter, because although the calls to bump-counter are within the scope of this local counter, the body of bump-counter isn't. The latter continues to refer to the global counter, whose final value is 6.

counter =>  6

counter는 이제 6이 되었다. 99로 세팅한 로컬 변수는 아무런 영향을 미치지 못했다.

아주 좋은 예이긴 하지만 실감이 나지 않을지도 모른다. 하지만  중요한  차이점이다.
좋은 예를 만들어 내는 것은 중요한 일이지만 일단 이 정도 예제로 대부분 이해하고 있으니 오늘은 여기까지 ..

(fluid-let은 비표준형의 스페셜 폼이며 마크로로 만들었다. 8.3 장에 만드는 방법이 있다.) 

Trackback 0 Comment 0