2009.03.21 21:10

동적 바인딩 생각해 보기 (1)

리스프에서 아마 가장 많은 문제를 일으킨 부분이 동적 바인딩과 FUNARG 문제일 것이다.


14.16 DYNAMIC SCOPING

14.16 은 gentle intro to symbolic computation 의 사실상  마지막 부분에 해당한다. 몇페이뒤의 4.18에서 책의 본문이 끝나기  때문이다.  이해하기가 조금 애매한 부분이기도 하다.  아무튼 개인적인 생각으로는 이 책의 설명이 제일 나은 것 같다. 다시 말하지만 개인적인 생각이다.

책의 마지막에 이르기까지 동적(dynamic) 스코프를 사용하지 않고 설명을 했고 저자는 마지막 부분에서 렉시컬 스코프와 동적 스코프의 구분을 하고 있다.

대부분의 언어들은 렉시컬 스코프를 적용하고 있고 동적 스코프는 별로 쓰이지 않는다.
그러나 한때는 리스프에서 동적 스코프가 표준이었다. 

동적 스코프에 대해서는 많은 오해가 있기 때문에 설명할 필요가 있다.   글을 쓰고 있는 사람도 예외가 아니다. 그러나 생각해 보면 매우 자연스럽기도 하다.  과거의 리스프 해커들은 동적 스코프를 썼을 뿐만 아니라 응용하고 기법화하기 조차 했다.

책의 해설을 잠깐 옮기면 foo 라는 함수가 X라는 변수에 접근하려면 foo라는 함수는 X라는 변수가 정의된 문맥 안에 존재해야 한다는 점이다.  만약 foo가 toplevel 에서 정의되었다면 foo가 접근할 수 있는 변수는 글로벌 변수와 함수안에서 정의된 로칼변수뿐이다.  그러나 bar 라는 함수안에 어떤 람다식으로 정의된 함수가 있다면 이 함수는 bar의 변수에 접근할 수 있고 자신의 로컬 변수에도 접근할 수 있다. Bar의 바깥에 정의된 함수들은 bar의 변수들에 접근할 수 없다,

동적 변수는 때로 스페셜변수라고도 부른다. 일단 변수 이름이 스페셜로 선언되면 이 변수는 어떤 함수에 대해서도 로컬변수가 아니다.  (이점은 정말 중요하다.) 변수는 어디서나 접근할 수 있는 것이다.  그러나 렉시컬 스코프의 변수는 정의된  몸체에서만 접근할 수 있다. 

스페셜 변수를 정의하는 방법의 하나는  DEFVAR macro 를 사용하는 것이다. 이를테면 (defvar birds)

이제 예를 들어보자
BIRDS 는  동적 스코프의 변수이고 FISH 는 렉시컬 스코프의 변수라고 하자.이제 이들에게 적당한 값을 주고 이들을 사용하는 함수도 하나 만들어 보자.

먼저 동적 변수를 하나 선언한다.
(defvar birds)

적당한 값을 만든다.

(setf fish ’(salmon tuna))
(setf birds ’(eagle vulture))


(defun ref-fish ()
fish)

(defun ref-birds ()
birds)

두개의 함수는 동적인 변수와  렉시컬 변수를 돌려준다.

(ref-fish) 
--> (salmon tuna)
(ref-birds) 
--> (eagle vulture)

이제 렉시컬 변수를 시험하는 함수를 만들어 보자.

(defun test-lexical (fish)
(list fish (ref-fish)))

인자로 받은 fish와 reffish 함수의 결과를 다시 리스트로 만든다.

> (test-lexical ’(guppy minnow))
((GUPPY MINNOW) (SALMON TUNA))

결과는 로컬 변수 fish를 돌려준다. 그리고 이 로컬 변수는 ref-fish에서는 접근할 수 없는 값으로 톱레벨에서 정의된 ref-fish는 글로벌 환경의 fish를 돌려준다. 그결과 로컬 변수로 (GUPPY MINNOW)를 글로벌 변수인 (SALMON TUNA)를 보여준다.

각각 다른 스코프를 쓰고 있다는 것을 보여준다.
책의 437 페이지에 있는 그림을 보면  동작 원리를 eval-trace 그림으로 보여준다.


동적스코프를 글로벌 변수와 혼동해서 설명하는 사람들이 많지만 사실은 다르다.

동적 스코프의 경우 (이전에 birds를 동적변수로 정의했다.)  결과는 하나는 같고 하나는 달라진다.  이차이는 defvar가 birds를 스페셜 변수로 만들어 놓았기 때문이다.

먼저 함수를 정의하자 . 이 함수는 birds를 사용한다.

(defun test-dynamic (birds)
(list birds (ref-birds)))

인자로 '(robin sparrow)를 사용했다.

> (test-dynamic ’(robin sparrow))

결과는  다음과 같다.
((ROBIN SPARROW) (ROBIN SPARROW))

  ref-birds가  주어진 인자와 같은 값을 낸 것이다. 

식의 계산이 끝나고 ref-birds는  원래의 값을 다시 돌려준다. 수행중에는 아니었다.
> (ref-birds)
(EAGLE VULTURE)

이런 이상한 답을 내어준 것은 다름이 아니다. TEST-DYNAMIC으로 진입하면서 스페셜 변수로 정의된 BIRDS 가 만들어진 것이고 몸체를 빠져 나가기 전까지 BIRDS는 이 값을 갖게된다. TEST-DYNAMIC 의 밖에 있는 함수 (ref-birds) 에서도 마찬가지다. BIRDS 라는 이름의 원래의 글로변 변수는 동적인 변수가 있는한 접근가능하지 않다.  test-dynamic  함수를 빠져 나오면서 함수의 인자인 BIRDS가 사라지면  원래의 BIRDS 값이 복원된다.

동적이라는 이름이 붙은 것은 REF-BIRDS의 BIRDS라는 이름의 변수의 값이 한 변수에 영구적으로 고정된 것이 아니라는 점이다. FISH 의 경우에는 고정되어 있었다, 그러나  BIRDS는 TEST-DYNAMIC로 들어가며 변수의 값이 새로 고정되고 나중에 원래의 값으로 돌아갔다. 

(이 과정은 바인딩이 일어날 때마다 항상 변한다. 예측이 불가능한 경우도 있다. 만약에 중간에 다른 바인딩이 일어났으면 도대체 어떤 BIRDS를 바인딩하고 있을지 정말 애매하다.

그리고 한가지 더 첨부하자면 birds는 함수의 범위를 무시하고 글로벌 함수인 ref-fish의 동작까지 영향을 주다가  영향력을 상실했다.)

스페셜 변수는 조심스레 사용되어야 한다. 초기의 리스프들에서  동적 스코프가 기본이었을 당시 한 함수는 다른 함수가 만든 변수의 값을 바꾸는 경우가 있었고 드물지만 문제를 일으키곤 했다.  렉시컬에서는 이런 일은 일어나지 않는다. 하지만 가끔씩은 동적 스코프의 변수의 사용이 합당한 경우가 있고 이들은 4.18에서 볼 것이다.


- 이제 4.16을 이해했으면 4.18을 이해하는 것은 아무런 문제가 없다.
이런 동적 바인딩과 렉시컬 바인딩을 차이나게 하는  근본적인 이유는  새로운 환경을 만드는 방법에 달려있다.

BIND함수가 환경을 어떻게 바인드하느냐에 따라 달라진다.
나중에 TAOI에서 자세히 설명하겠다.

만약 그때까지 기다리기가 어려우면 다른 자료들을 열심히 찾아보면 된다.
(그러나 이 글을 다시 한번 읽게 될 것이다. )
Trackback 0 Comment 0