페북을 뒤적이다 우연히 폴 그레이엄의 번역글을 읽게 되었다.

꽤나 괜찮은 글이었는데 전문 번역이 되어 있지 않아 그냥 한 번 끄적여 본다.  


아래 글에서는 컴퓨터 언어와 관련되어 사용되는 고유한 의미의 단어들이 다수 사용되고 있다. 예를 들어 리스트(list), 평가(evaluation), 해석 등 프로그래밍 언어에서만 통용되는 별도의 의미를 지니고 있는 단어들이다. 사실 이를 번역 하기가 쉽지는 않다. 아래에서는 문맥에 따라 원어, 번역어를 혼용해서 사용하고 있다. (그냥 귀찮아서 그렇게 했다.) 하지만 혼동이 될만한 경우에는 원문을 괄호에 함께 표기해 놓았다. 이것은 읽는 사람들이 잘 판단하길 바란다. 또한 원문에는 없는 내용들도 상황에 맞겠다 싶은 경우 그냥 내 마음대로 추가해 놓았다. 그러므로 원문에 없는 내용이라고 이상하게 여기지 말기 바란다.

 

마지막으로 모든 각주에는 원 저자가 작성한 각주 외에도, 이해를 돕기 위해 원문에 없는 내용도 다수 포함되어 있다. 



원문 : http://www.paulgraham.com/rootsoflisp.html





The Roots of LISP

폴 그레이엄


내가 이 글을 쓴 이유는 존 메카시가 발견한 것이 과연 무엇이었는가를 나 자신에게 명확하게 이해시키기 위함이다. 물론 Lisp 언어 자체를 배우고자 하는 사람에게는 이 글이 썩 도움이 되지는 않겠지만, 그래도 Lisp의 본질을 이해하고 싶어하는 사람들에겐 충분히 도움이 될 거라 믿어 의심치 않는다. 여기서 말하는 Lisp의 본질이란 'Lisp이 왜 만들어졌는가', 'Lisp에서 사용되는 문법에 대한 원리는 무엇인가?' 이 두 가지를 의미한다. 그리고 사실 이런 문법적 원리들은 Lisp이 지닌 특징 중 하나일 뿐이며 Lisp과 관련된 파생 언어(사투리)들이 왜 이렇게 많은지를 알 수 있게 하는 열쇠이다.


1960년에 존 매카시는 정말 놀랄만한 논문 하나를 발표하였는데, 이 논문은 유클리드 기하학과 관련된 내용들을 프로그래밍하는 내용을 담고 있다. 이 논문을 통해 매카시는 간단한 연산자 몇 개와 함수 표기법을 제시하였고 이를 이용해서 완전한 하나의 프로그래밍 언어를 만들어 낼 수 있게 하였다. 그리고 그는 이 언어를 Lisp이라 칭하였다. 이 말은 "List Processing"을 줄인 말이다. 이 말이 결코 틀린 말은 아닌데, 실제로 이 언어의 핵심 아이디어는 코드(code)와 데이터(data)를 저장하기 위해 리스트(list)라고 하는 간단한 자료구조를 사용하고 있기 때문이다.

 

여기서 매카시가 발견한 것이 무엇인지를 이해하는 것이 매우 중요하다. 이를 단순하게 컴퓨터 발전사에 몇 개의 족적을 남긴 정도의 일로 생각해선 절대 안될 말이고, 우리들이 현재 사용하고 있는 컴퓨터 언어들에 대한 모델을 발견했다는 것으로 이해해야 한다. 솔직히 나는 이 세상에 프로그래밍 모델이라고 말할 수 있는 모델은 딱 두 가지 밖에 없다고 생각한다. 바로 CLisp 이다. 이건 정말 명료하다. 게다가 이 둘은 양 극단에 놓여 있고 건너기 힘든 강이 그 사이를 흐르고 있다.

컴퓨터가 더욱 강력해지면서 Lisp 모델로 나아가고 있는 새로운 언어들이 계속해서 생겨나고 있다. 물론 지난 20년간 새로운 언어들이 취한 언어 모델은 C 모델이었다. 하지만 여기에 동적 타이핑이나 가비지 컬렉션과 같은 Lisp 모델의 장점들이 계속 더해지고 있다.


이 글에서 나는 매카시가 발견한 사실을 최대한 쉽게 풀어쓰려고 노력할 것이다. 중점을 두어야 할 것은 여러분은 40년 전[각주:1]에 발견된 이론 자체를 이해하려고 노력할 것이 아니라 언어가 지향하고 있는 것이 무엇인지 이해하고자 노력해야 한다는 것이다. Lisp이 가진 기기묘묘한 특징은 Lisp 스스로 기술될 수 있다는 것이다. (사실 이는 Lisp이 무언가를 정의하는 능력이 있기 때문이다.) 과연 이 말이 무슨 의미를 뜻하는지 이해하기 위해서 매카시가 해왔던 작업들을 다시 살펴보고 그가 작성한 수학적 표기법들을 Common Lisp으로 작성해 보도록 한다.




Seven Primitive Operators


가장 먼저 표현식(expression)을 정의한다. 표현식이란 atom[각주:2], 0개 이상의 표현식을 포함한 리스트[각주:3](list), 공백문자(WS), 괄호[각주:4](parentheses)를 의미한다.

 

몇 가지 표현식을 살펴보자.


foo

()

(foo)

(foo bar)

(a b (c) d)


마지막 표현식은 4개의 원소를 가진 리스트이다. 여기서 세번째 원소는 하나의 원소를 가진 리스트이이다.


수학에서 1+1 이란 표현식은 2 라는 값을 가진다. 마찬가지로 올바른 Lisp 표현식은 반드시 을 가진다. 어떤 표현식 가 값 를 산출하면 이를 를 반환한다(e returns v)고 한다.[각주:5] 어떤 종류의 표현식이 가능하고 어떤 종류의 값이 반환되는지 계속 살펴보자.


여기에 리스트로 주어진 어떤 표현식이 있다고 하자. 이 경우 첫 번째 원소를 우리는 연산자(operator)라고 부른다. 그리고 다음 원소부터 남아있는 모든 원소들을 인자(arguments)라고 부른다. 우리는 총 7개의 기본(primitive[각주:6]) 연산자들을 정의할 것이다. (quote, atom, eq, car, cdr, cons, cond)



1. (quote x) x 를 반환한다. 가독성을 높이기 위해 (quote x)를 'x 와 같이 표기할 수 있다.[각주:7] 

> (quote a)

a

> 'a

a

> (quote (a b c))

(a b c)



2.
(atom x)는 만약 x 값이 atom이거나 빈 리스트라면 atom 를 반환한다. 아닌 경우 빈 리스트를 반환한다. Lisp에서는 atom t 가 (truth)을 나타내고 빈 리스트는 거짓(falsity)을 나타낸다.

> (atom 'a)

t

> (atom '(a b c))

()

> (atom '())

t


이제 연산자의 인수가 어떻게 평가(evaluate)되었는지를 통해 quote 가 하는 일을 이해할 수 있다. 리스트를 quote 처리하면 이 리스트는 평가(eval)를 수행하지 않는다. 반대로 quote 처리를 하지 않은 리스트에 포함된 atom은 코드로 처리되어 결국 평가(eval)된다.


> (atom (atom 'a))

t


quote 처리된 리스트의 경우 그냥 평범한 리스트로만 고려된다. 아래의 경우에는 quote 처리된 리스트의 경우 그냥 2개의 원소를 가진 리스트로 처리된다. - 즉, quote 내부의 atom은 평가(eval)되지 않는다.

> (atom '(atom 'a))

()


이런 방식은 영어에서 인용문을 처리는 방식과 매우 비슷하다.

(1) 캠브릿지에는 약 9만명의 인구가 살고 있다.

(2) '캠브릿지'는 총 4글자이다.

여기서 첫번 째의 경우에서 캠브릿지는 지역을 의미하고 있지만 두번 째의 경우 글자 그 자체를 의미한다.


quote 라는 것은 어찌보면 좀 낮선 개념인데 실은 다른 언어에서는 이런 기능을 처리하는 물체가 거의  존재하지 않기 때문이다. quote는 Lisp이 가진 아주 특별한 능력에 아주 밀접하게 연관되어 있다. 여기서 특별한 능력이란 바로 코드(code)와 데이터(data)를 동일한 자료 구조에 담을 수 있는 것을 의미한다. 여기서 quote는 자료구조에 담긴 코드와 데이터를 구분 짓는 역할을 담당하게 된다.



3. (eq x y) 는 만약 x와 y가 같은 atom이거나 둘 다 빈 리스트인 경우 t 를 반환한다. 그 외에는 빈 리스트를 반환한다. (즉, 참/거짓 반환)

> (eq 'a 'a)

t

> (eq 'a 'b)

()

> (eq '() '())

t



4. (car x)x 위치에 리스트가 와야 한다. 리스트 내 첫 번째 원소를 반환한다.

> (car '(a b c))

a



5. (cdr x)x 위치에 리스트가 와야 한다. 리스트 내 두 번재 원소 이후 남은 모든 원소를 반환한다.

> (cdr '(a b c))

(b c)



6. (cons x y)의 값이 리스트로 제공되어야 한다. x의 값 뒤로 의 값을 합친 리스트를 반환한다.

> (cons 'a '(b c))

(a b c)

> (cons 'a (cons 'b (cons 'c '())))

(a b c)

> (car (cons 'a '(b c)))

a

> (cdr (cons 'a '(b c)))

(b c)

 

 

7. (cond (p1 e1) ... (pn en)) 는 다음과 같이 평가된다. p 표현식은 하나의 가 반환될 때까지 계속 평가한다. t 가 발견되면 e 표현식에 대응되는 값을 반환한다. 이 때 그 값은 cond 표현식에 의해 평가된 값으로 간주되어 반환된다.

> (cond ((eq 'a 'b) 'first)

             ((atom 'a) 'second))

'second


7개의 필수 연산자 중 5개의 경우에 해당 연산자로 시작하는 표현식인 경우 인자가 모두 평가된다. 우리는 이런 형태의 연산자들을 함수(function) 타입이라고 부른다.[각주:8]




Denoting Functions


다음으로 함수를 표기하는 방법을 정의해보자. 함수는 다음과 같이 표기한다.
 (lambda (p1...pn) e)

여기서 p1..pn 는 모두 atom 이어야 한다. (이걸 파라미터라고 부른다) 는 표현식이다. 어떤 표현식의 첫번째 원소가 다음과 같다면, (첫 번째 원소를 눈에 잘 띄도록 파란색으로 칠해 놓았다.)
((lambda (p1...pn) e) a1...an)

우리는 이걸 "함수 호출"이라고 부른다.

 

이에 대한 처리 방법은 우선 각각의 a 들을 모두 먼저 
평가(eval)한 다음 를 평가(eval)한다.[각주:9]
e 표현식을 평가하는 도중 그 안에 포함된 pi 들의 값은 앞서 평가된 ai 의 값들로 치환을 하여 사용하게 된다. (pi ai 로 대치된다고 생각하면 된다.)
> ((lambda (x)(cons x '(b))) 'a)
(a b)
> ((lambda (x y) (cons x (cdr y))) 'z '(a b c))
(z b c)

어떤 표현식의 첫번 째 원소가 atom f 인 경우를 보자. 여기서 f 는 기본 연산자에는 속하지 않는다. 
 

(f a1...an)


이 때에는 의 값은 함수로 고려된다. - 이 때 (lambda (p1...pn)) 이다.- 따라서 이 표현식의 값을 구하기 위해서는,
((lambda (p1...pn) e) a1...an)

위와 같이 처리하게 된다. 

e 표현식 내에서 파라미터들을 연산자처럼 사용할 수 있다.[각주:10] 
> ((lambda (f) (f '(b c))) 
   '(lambda (x) (cons 'a x)))
(a b c)
간단히 설명하자면 (b c) 리스트에 어떤 연산을 하는 연산자를 파라미터로 받는 함수를 정의하고 있다. 여기에 항상 맨 앞에 a를 붙여주는 함수를 인자로 넘기고 있다.

Lisp에서는 함수 자체를 표현하기 위한 방법으로 또 다른 표기법을 제공한다. 이는 재귀함수[각주:11]를 사용할 때 매우 편리하다.[각주:12]
(label f (lambda(p1...pn) e))

를 평가하는 중간에 가 발견된다면 는 label 표현식에 의해 정의된 값을 사용하게 된다. 마치 파라미터처럼 가 인자로 입력된 것과 같은 효과를 가지게 된다.[각주:13]

함수 (subst x y z) 를 정의한다고 해보자. 여기서 는 표현식, 는 atom, 는 리스트이다. 그리고 리턴값으로는 를 반환한다. 단, z 내에 가 포함되어 있다면 이를 모두 x로 바꾼다. (물론 중첩된 리스트라도 상관없이 처리되어야 한다.)
> (subst 'm 'b '(a b (a b c) d))
(a m (a m c) d)

이를 구현해보자.
(label subst (lambda (x y z)
                   (cond ((atom z))
     (cond ((eq z y) x)
                  ('t z))
                   ('t (cons (subst x y (car z))
                      (subst x y (cdr z)))))))

이걸 f = (label f (lamba (p1...pn) e)) 로 줄여서 쓸 수 있는 방법이 있다.

(defun f (p1...pn) e)

이런 방식을 이용해 다시 기술하면 다음과 같다.
(defun subst (x y z)
  (cond ((atom z)
            (cond ((eq z y) x)
                    ('t z)))
                  ('t (cons (subst x y (car z))
                 (subst x y (cdr z)))))))

덤으로 위의 예제를 통해 cond 표현식의 기본 형식을 확인할 수 있다. 첫 번째 원소가 't 이면 해당 절은 항상 성공한다.

(cond (x y) ('t z))

이는 if x then y else z 를 의미한다.



Some Functions


자, 지금까지 함수를 표기하는 방법을 배웠다. 이제는 앞서 배운 7개의 기본 연산자와 관련된 함수를 배워야할 차레이다. 그 전에 먼저 편리하게 Lisp을 사용하기 위한 일반적인 패턴 몇 가지를 확인하고 넘어가자.

먼저 cr 이라는 것이 있다. 여기서 x 에 올 수 있는 값은 a 또는 d 라는 값을 가지는 연속된 값이다. 물론 말할 것도 없이 car, cdr도 이 범주에 속한다. (자 이제 우리가 무엇을 하려고 하는지 알았을 것이다.) 예를 들어 (cadr e) 라는 표현식을 처리한다고 하자. 이건 (car (cdr e)) 를 의미한다. 결국 이 표현식은 의 두번째 원소를 반환하라는 것을 의미한다.
> (cadr '((a b) (c d) e))
(c d)
> (caddr '((a b) (c d) e))
e
>  (cdar '((a b) (c d) e))
(a b c)


자 이제 새로운 함수들을 몇 개 정의해 보자. 
앞으로 나올 함수의 이름에는 모두 점(.)이 하나씩 붙어 있는데 이건 Lisp에서 제공하는 기본적인 함수 이름과 구별짓기 위해 그냥 임의대로 붙인 것이다. 마찬가지로 Common Lisp에서 제공하는 동일한 이름들을 피하기 위해서도 사용되었다.


1. (null. x)는 인수가 빈 리스트인지를 확인하는데 사용한다.
(defun null. (x)
    (eq x '()))

> (null. 'a)
()
> (null. '())
t


2 (and. x y)는 두 인자가 모두 t 를 반환하는 경우에 를 반환한다. 아닌 경우 빈 문자열을 반환한다.
(defun and. (x y) 
    (cond (x (cond (y 't) ('t '()))) 
             ('t '()))) 

> (and. (atom 'a) (eq 'a 'a)) 
> (and. (atom 'a) (eq 'a 'b))
()


3. (not. x)는 인자가 빈 리스트를 반환하는 경우 t를, 아닌경우 빈 리스트를 반환한다.
(defun not. (x)
    (cond (x '())
             ('t 't)))

> (not (eq 'a 'a))
()
> (not (eq 'a 'b))
t


4. (append. x y)는 두 리스트를 받아서 이를 합친 리스트를 반환한다.
(defun append. (x y)
    (cond ((null. x) y)
             ('t (cons (car x) (append. (car x) y)))))

> (append. '(a b) '(c d))
(a b c d)
> (append. 'a() '(c d))
(c d)


5. (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))

 
6. (assoc. x y)는 atom 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)))
>(assoc. 'x '((x new) (x a) (y b)))
new




The Surprise


지금까지 표현식을 다른 것으로 치환하거나 합치거나 하는 등의 여러 함수들을 살펴보았다. 아마도 당신은 이것이 꽤 훌륭하다는 것을 인정할 것이다. 하지만 이걸 도대체 어디에다 써 먹어야 하는가? 자 이제 다들 놀랄 준비를 하시라. 이제 우리가 만든 언어를 해석(interpret)하는 기능을 가진 함수를 작성해 보도록 할 것이다. 이 함수의 입력 값은 Lisp의 표현식이다. 그리고 이에 대한 값을 반환한다.

전체 코드는 다음과 같다.

(defun eval. (e a)
  (cond
   ((atom e) (assoc. e a))
   ((atom (car e))
    (cond
     ((eq (car e) 'quote) (cadr e))
     ((eq (car e) 'atom)  (atom   (eval. (cadr e) a)))
     ((eq (car e) 'eq)    (eq     (eval. (cadr e) a)
                                  (eval. (caddr e) a)))
     ((eq (car e) 'car)   (car    (eval. (cadr e) a)))
     ((eq (car e) 'cdr)   (cdr    (eval. (cadr e) a)))
     ((eq (car e) 'cons)  (cons   (eval. (cadr e) a)
                                  (eval. (caddr e) a)))
     ((eq (car e) 'cond)  (evcon. (cdr e) a))
     ('t (eval. (cons (assoc. (car e) a)
                      (cdr e))
                 a))))
   ((eq (caar e) 'label)
    (eval. (cons (caddar e) (cdr e))
           (cons (list. (cadar e) (car e)) a)))
   ((eq (caar e) 'lambda)
    (eval. (caddar e)
           (append. (pair. (cadar e) (evlis. (cdr e) a))
                    a)))))

(defun evcon. (c a)
  (cond ((eval. (caar c) a)
  (eval. (cadar c) a))
('t (evcon. (cdr c) a))))

(defun evlis. (m a)
  (cond ((null. m) '())
('t (cons (eval.  (car m) a)
                  (evlis. (cdr m) a)))))

이것이 바로 eval 함수의 정의이다. 지금까지 본 코드 중에 가장 길다. 이건 좀 하나 하나 살펴보도록 하자.

이 함수는 2개의 인자를 받는다. 는 평가(eval)를 해야할 표현식이고 a 는 함수 호출시 마치 파라미터가 동작하는 것과 유사하게 특정 atom들에게 실제 값을 부여할 수 있도록 그 값들을 담고 있는 리스트이다. 우리는 이 리스트를 환경(environment)이라고 부른다. 이 리스트는 pair. 함수에 의해 생성된 리스트의 형태를 취하고 있다. 이 리스트를 만들거나 혹은 어떤 값을 찾기 위해서는 pair.assoc. 함수를 사용한다.

eval. 은 cond 표현식에 의해 4개의 절(clause)로 분기된다. 어떻게 표현식을 평가할 것인가는 표현식이 어떤 종류인가에 따라 다르다. 우선 첫번째 절은 표현식에 atom이 오는 경우를 다룬다. 가 atom인 경우 이를 환경(env)에서 찾아 그 값을 반환한다.
>(eval. 'x '((x a) (y b)))
a
 
 두번 째 절은 (a ...) 과 같은 표현식을 다룬다. 물론 여기서 atom 이다. 여기에는 앞서 살펴 본 기본(primitive) 연산자도 사용하는 경우이며 내부에서 다시 cond에 의해 세부 절로 분기된다. 
> (eval. '(eq 'a 'a) '())
> (eval. '(cons x '(b c))
            '((x a) (y b)))
(a b c) 
 
여기서는 quote를 제외하고 인자의 값을 계산하기 위해 모두 eval. 를 다시 호출한다. 두번 째 절에 속한 세부 절들 중에 마지막 2개의 절은 좀 복잡한다. cond 표현식 자체를 평가(eval)하기 위해추가적으로 evcon. 이라는 함수를 호출해야 한다. 이 함수는 재귀적으로 표현식의 원소들을 평가하면서 가장 먼저 를 반환하는 경우를 찾는다. 그리고 이를 발견하게 되면 찾은 결과의 두 번째 원소(즉, pred와 쌍이 되는 표현식)평가한 뒤 돌려준다.

> (eval. '(cond ((atom x) 'atom)
                     ('t 'list)) 
            '((x '(a b))))
list 
 
두번째 절에 속한 절들 중에 맨 마지막 절은 파라미터처럼 전달되는 함수들을 처리하기 위해 제공되는 것이다. 이는 atom을 값으로 치환을 하는 작업을 의미한다. (물론 이 형식은 lambda나 label 표현식과 같은 형태를 취해야 한다.) 최종적으로 이렇게 얻어진 표현식을 평가하여 반환한다.

따라서
> (eval. '(f '(b c)) 
            '((f (lambda (x) (cons 'a x))))) 

이것은 다음과 같이 변환된다.[각주:14]
> (eval. '((lambda (x) (cons 'a x)) '(b c)) 
            '((f (lambda (x) (cons 'a x)))))

위의 식의 경우 최종 결과로 (a b c) 를 얻게 된다.

이제 다음으로 넘어가자. eval. 함수가 분기하는 마지막 2개의 절은 lamba 혹은 label 표현식을 다루고 있다. label이 하는 일은 함수의 이름과 실제 그 함수 자체를 환경(env)에 밀어 넣는 것이 전부이다. 그리고 나서 eval. 함수를 호출하면 label 표현식에 의해 치환된 lambda 함수가 평가되는 것이다. 그래서,
(eval. '((label firstatom (lambda (x)
                                    (cond ((atom x) x) 
                                            ('t (firstatom (car x)))))) 
         y) 
         '((y ((a b) (c d)))))  

이것은 다음과 같이 변환된다.
(eval. '((lambda (x) 
             (cond ((atom x) x) 
                     ('t (firstatom (car x))))) 
         y) 
         '((firstatom 
             (label firstatom (lambda (x) 
                                       (cond ((atom x) x) 
                                               ('t (firstatom (car x))))))) 
         (y ((a b) (c d))))) 

최종적으로 a 가 반환된다.

이제 마지막이다. 마지막은 ((lambda (p1...pn) e) a1...an) 을 평가하기 위해 사용된다. 가장 먼저 (a1...an)에 대응되는 값 (v1...vn을 얻기 위해서 evlis. 가 호출된다.다음으로 얻어진 (v1...vn)을 이용하여 쌍으로 이루어진 ((a1 v1)...(an vn))를 만들어 이를 환경에 추가한다. 
(eval. '((lambda (x y) (cons x (cdr y))) 
           'a 
           '(b c d)) 
         '()) 

이것은 다음과 같이 변환된다.
(eval. '(cons x (cdr y)) 
         '((x a) (y (b c d)))) 

최종적으로 (a  c d) 를 얻게 된다.





Aftermath

우리는 지금까지 eval 함수가 어떻게 동작을 하는지 살펴보았다. 다시 한번 이 의미에 대해 생각해보자. 여기서 우린 정말 우아한 계산 모델을 살펴 보았다. quote, atom, eq, car, adr, cons, cond 를 이용해서 최종적으로 eval. 함수를 구현하였고 이를 이용해 우리가 원하는 기능을 언제든지 언어에 추가해 볼 수 있다.
사실 이 이전에 튜링 머신이라는 아주 중요한 계산 모델이 존재했으나 그리 널리 알려지지는 않았다. 매카시는 알고리즘을 표현하기 위한 언어와 추상화를 위한 도구를 위해 Lisp을 만들었다.

사실 1960년에 이 언어가 처음 나왔을 때에는 많은 부분이 고려되지 못했다. 이 언어는 side-effect[각주:15]가 없었으며 순차적인 계산을 할 수 없었다. (어쨌거나 이것은 side-effect가 있는 환경에서나 유용하긴 하다.) 또한 실용적인 숫자 체계와 동적 스코핑 기능 등은 존재하지 않았다. 하지만 이런 제약들은 별 문제가 되지 않는 것이 약간의 코드만 추가하면 이런 기능을 추가할 수 있기 때문이다. Steele과 Sussman의 논문 "The Art of the Interpreter"를 통해 이를 잘 설명하고 있다.

여러분이 매카시가 제안한 eval 을 제대로 이해할수만 있다면, 여러분은 컴퓨터 언어가 만들어진 역사를 좀 더 잘 이해할 수 있게 된다. 앞서 살펴본 아이디어는 오늘날의 Lisp에서도 여전히 유효한 핵심 문법이다. 따라서 매카시가 작성한 최초의 문서를 살펴보는 일은 Lisp이 진정 무엇인지를 이해할 수 있도록 해준다. Lisp은 매카시가 발견한 그 무언가를 표현하기 위해 만들어진 것도 아니고, AI를 위한 언어나 빠른 개발 주기를 이루어내기 위한 언어도 아니다. 오히려 여러분이 명확한 계산 작업이 필요할 때 바로 사용할 수 있는 유용한 도구인 것이다.

 

 어디하나 모난데 없는 평범한 개발자들이 일상적으로 사용하는 언어들이 점점 더 Lisp의 모습을 닮아가고 있다. 여러분이 eval 을 이해한다면 계산 모델이 지향하고 있는 미래의 모습들을 쉽게 이해할 수 있으리라.[각주:16] 



@ 원문에 있는 Note는 중요하지 않아 생략한다.



  1. 이 글 또한 약 10년 전 글이다. [본문으로]
  2. foo 와 같은 문자열을 예로 들수 있다. 리스트 형식이 아닌 최소한의 의미 단위라고 생각하면 된다. [본문으로]
  3. 즉, 빈 리스트도 포함된다. [본문으로]
  4. 여기서는 열린 괄호와 닫힌 괄호가 모두 존재하는 완전한 괄호를 의미 [본문으로]
  5. 이는 평가(evaluation)라는 과정을 거쳐 값을 반환하게 된다. eval이라고 간단하게 표기하기도 한다. [본문으로]
  6. primitive 라는 의미는 '붙박이' 등으로도 해석할 수 있는데 보통 해당 언어가 기본으로 제공하는 기능을 의미한다. C언어에서의 int, double 등이 primitive type이다. [본문으로]
  7. 내부적으로는 달라지는 것 없이 사용자에게 편의를 제공하는 것을 의미하므로 이를 sugar code라고도 한다. [본문으로]
  8. 그렇다면 함수형에 속하지 않는 연산자는 무엇인가? 바로 quote와 cond다. 이 두 연산자는 좀 다르게 평가(eval)된다. quote가 평가될 때에는 인자들은 평가되지 않는다. 그러나 quote가 사용된 표현식 전체가 값으로 반환될 때에는 평가가 된다. 그리고 올바른 cond 표현식의 경우에는 부분 표현식 L-shaped 경로만이 평가된다 [본문으로]
  9. 인자가 모두 평가된 뒤 값을 전달하는 구조이므로 이를 통해 암묵적으로 call by value가 적용되어 있다는 것을 확인할 수 있다. [본문으로]
  10. 구문이 좀 혼동될 수 있는데 파라미터 f 를 함수 본문에서 연산자처럼 사용한다. C언어에서 함수 포인터를 파라미터로 받아서 함수 본문 내에서 이를 호출하는 형태를 생각하면 된다. [본문으로]
  11. 자기 자신을 호출하는 함수를 말한다. [본문으로]
  12. 물론 논리적으로는 이런 표기법이 반드시 필요하지는 않다. 기존의 표기법으로도 얼마든지 재귀 함수를 표현할 수 있기 때문이다. 이를 Y combinator라고 한다. 하지만 매카시가 논문을 쓸 당시에는 Y combinator의 존재를 아마 몰랐을 것이다. 어찌되었든 여기서 제시하고 있는 label 표기법이 더 읽기 쉽다는 사실에는 변함이 없다. [본문으로]
  13. 사실 label은 함수에 대해서만 특별한 어떤 작업을 수행해주는 것이 아니라 어떤 범위 안에서 통용될 이름을 부여하는 것이다. [본문으로]
  14. 주어진 코드를 잘 보면 f는 환경으로 주어지는 것을 알 수 있다. [본문으로]
  15. 부수효과. 특정 상태를 환경에 저장할 때 발생하는 것을 예로 들 수 있다. 좀 더 자세한 사항은 PL 책들을 참고한다. [본문으로]
  16. 아무래도 그레이엄 아저씨는 C 모델을 가장 하위에, Lisp모델을 최 상위에 놓고 C++, Java, C# 등의 주류 언어들을 C모델에서 Lisp모델로 진화하는 과정으로 여기는 것이 아닌가 한다... [본문으로]


정말 100만년만에 PC 좀 바꿔보려고, 또 이왕 하는거 좀 고사양으로 맞추려고 나름 고민해가며 만든 견적을 주문한 날 샌디 사태 발생.

그냥 사서 리콜할까도 고민해봤지만 영 마음이 내키지 않아 모두 취소.
다행히 설 연휴로 인해 배송을 시작도 안 한 상태였음.

한 가지 위안을 삼을 만한 것은, 나름 방안을 강구해 보겠다고 메인보드 칩셋이니 하는 것 따위를 뒤져보고 나서야...
아 ! 세상이 참 빨리도 변하는구나.. 내가 알던 지식은 이제 아무 짝에도 쓸모가 없다는 걸 알게된 것.

예전에는 당연하다고 생각했던 컴퓨터 Architecture가 이미 사장된 것들이 참 많네.. 새로운 것도 많고...
다시 맘 잡고 한 번 살펴봐야 겠다는 생각이 든다.

갑자기, 학교 교수님들은 이런 거 어떻게 공부해서 애들 가르칠까하는 걱정이 들었다...
그냥 또 구닥다리 내용을 가지고 밀어 붙이시는 것은 아니신지...
- 나 학교 다닐 때에도 이런 것 때문에 고생했는데... 실컷 수업 듣고 내 준 숙제 풀면 더 이상 지원 않는 instruction.. -..-) ;


덧)

심심해서 회사에서 쓰는 네할렘 장비들은 어느 정도인가 알아봤더니... 가격이 덜덜덜...


emacs 설치 팁...

MindStorm 2010.11.29 16:06

오랜만의 포스팅임에도 불구하고 짧게...

emacs 설치시 다음과 같은 에러 메시지를 얻었다면.

 configure: error: a system implementation of alloca is required

이 경우에는 다음과 같이 설치한다.

 ./configure --without-x

대부분 X환경 때문이다. 난 뭐 X를 안쓰니...


아, 이것 때문에 얼마나 많은 시간을 가열차고 쓸모없이 고민 고민했던가 !