Lisp를 두드러지게 하는 것 중 하나는 진화에 적합하도록 설계되었다는 점입니다. 여러분은 Lisp를 사용해서 새로운 Lisp 연산자를 정의할 수 있습니다. 객체 지향 프로그래밍 같은 새로운 추상화 방법이 인기를 끌게 되었을 때, 객체 지향 기능도 역시 Lisp로 쉽게 구현할 수 있었습니다. 이런 언어는 마치 DNA처럼 유행을 타지 않죠.
새로운 도구
왜 Lisp를 배워야 하냐구요? 굳이 이 언어가 아니어도 여러분은 다른 언어로 원하는 일을 얼마든지 할 수 있을텐데 말입니다. 가령 n 보다 작은 숫자들의 합을 구하는 함수를 작성한다고 해봅시다. 이 정도는 Lisp나 C나 별반 차이가 없습니다.
;Lisp
(defun sum(n)
(let ((s 0))
(dotimes (i n s)
(incf s i))))
/- C *-
int sum(int n) {
int i, s = 0;
for(i=0; i < n; i++)
s += i;
return(s);
}
여러분이 만약 이렇게 단순한 코드만 만들거라면, 어떤 언어를 쓰든지 별 상관이 없습니다. 그렇지만 만약 숫자 n을 매개변수로 받아서, 인수에 n을 더하여 반환하는 함수를 만들어주는 함수를 작성하려고 한다면 어떨까요?
;Lisp
(defun addn (n)
#'(lambda (x)
(+ x n)))
이런 addn 함수를 C로 작성하려면 어떻게 해야될까요? 당장 생각해내기는 꽤 어려울 겁니다.
이런 기능을 대체 누가 원하냐구요? 프로그래밍 언어는 여러분이 언어로 표현할 수 있는 것 외에는 생각하지 못하도록 만드는 힘을 가지고 있습니다. 어떤 언어로 작성할 수 있는 프로그램은 생각해 낼 수 있지만, 반대로 여러분이 설명조차 할 수 없는 대상을 상상하기는 매우 어렵습니다. 제가 베이직으로 처음 프로그램을 작성하는 법을 배울 때를 돌이켜보면, 그 때는 재귀 호출이 없어서 답답하다고 생각하진 않았습니다. 그게 뭔지도 몰랐으니까요. 그냥 베이직 수준으로 생각할 수 있는 범위가 제한되는거죠. 애초에 알고리즘도 반복문으로 구현할 수 있는 것만 떠올릴 수 있는데, 재귀 호출이 왜 필요했겠습니까?
뭐, 여러분이 Lexical Closure가 필요하지 않다고 느낀다면 (이게 앞의 예제에서 다룬 기능입니다.) "지금은 그냥 믿어주세요" 라고 말할 수 밖에 없겠지만, Lisp 프로그래머는 일상적으로 이 기능을 사용합니다. Common Lisp로 작성된 어떤 길이의 프로그램이든 클로저를 이용하지 않은 코드를 발견하는게 어려울 정도입니다. 112쪽을 한 번 보시기 바랍니다.
그리고 클로저는 다른 언어에서 찾을 수 없는 추상화 방법 중 단 하나일 뿐입니다. 훨씬 더 강력한, Lisp의 또다른 특징은 바로 Lisp 프로그램이 Lisp 자료 구조를 통해 표현된다는 점입니다. 이게 의미하는 바가 무엇이냐 하면, 프로그램을 생성하는 프로그램을 작성할 수 있다는거죠. 음, 사람들이 정말 이런 기능을 원하냐구요? 물론이죠! - 그리고 이 기능은 매크로라고 불리며, 숙련된 프로그래머라면 항상 사용하는 기능이죠. 한 번 만들어보려면 173쪽을 보세요.
매크로, 클로저, 실행 시간 타입 계산으로 객체 지향 프로그래밍을 뛰어넘을 수 있습니다. 만약 방금 한 말을 이해하셨다면 이 책을 덮으셔도 됩니다. 왜 그런지 설명할 수 있다면 Lisp를 잘 알고 있을테니까요. 이 내용은 매우 중요하기 때문에 17장 전체를 할애해서 객체 지향 언어를 Lisp로 구현하는 방법을 상세히 설명해두었습니다.
2장부터 13장까지는 17장에 있는 코드를 이해하는데 필요한 개념들을 하나씩 소개하겠구요. 여러분이 앞으로 쏟아부을 노력으로 받게 될 보상은 이렇게 말할 수 있겠네요. 숙련된 C++ 프로그래머가 Basic으로 프로그래밍할 때 느끼는 것만큼이나 앞으로는 C++로 프로그래밍할 때 질식할 것 같은 기분을 느끼게 될겁니다. 그런 의미에서 Lisp는 그냥 새로운 언어를 하나 더 배우는 정도가 아니죠. 프로그램을 작성하는데 필요한 새롭고 강력한 사고의 틀을 배우게 되는 겁니다.
새로운 기법
앞에서도 이야기했지만, Lisp는 다른 언어들이 주지 못하는 도구들을 제공합니다. 그런데 여기에서 그치지 않습니다. Lisp와 같이 딸려오는 것들 - 자동 메모리 관리, 동적인 타입 계산, 클로저, 기타 등등 - 이 프로그래밍을 훨씬 쉽게 만들어줍니다. 이 모든 것들이 합쳐져서 새로운 프로그래밍 방법을 만들어 냅니다.
Lisp는 확장 가능하도록 설계되었습니다: 연산자를 마음대로 정의할 수 있죠. Lisp 언어 자체가 여러분의 프로그램에 있는 함수나 매크로들이랑 똑같은 방식으로 만들어져 있기 때문에 가능한 일입니다. 프로그램을 작성하는 것과 Lisp를 확장하는 것이 전혀 차이가 없지요. 사실 언어를 확장하는 일이 대단히 일상적입니다. 프로그램을 언어로 변환해가면서 작성하는 것과 반대로 언어를 발전시켜서 프로그램으로 만드는 것이 가능합니다. 위에서 아래로 내려가는 방식이나, 아래에서 위로 올라가는 방식이나 모두 쓸 수 있습니다.
어떤 프로그램이든지 그 자신의 목적에 딱 맞는 언어를 가질 수 있으면 좋지만, 특히 프로그램의 복잡도가 증가할수록 아래에서 위로 프로그래밍하는 것이 더 가치가 있습니다. 아래에서 위로 작성된 프로그램은 여러 개의 계층으로 구성될 수 있는데, 각각은 그 위쪽 계층에서 사용하는 일종의 언어라고 말할 수 있습니다. TeX이 이런 방식으로 작성된 초창기 프로그램들 중 하나입니다. 어떤 언어로도 아래에서 위로 프로그램을 작성할 수 있기는 하지만, Lisp는 매우 자연스럽게 이런 스타일을 지원한다는 점에서 차이가 있습니다.
아래에서 위로 프로그래밍하는 것은 소프트웨어를 자연스럽게 확장하는 길입니다. 만약 아래에서 위로 프로그래밍하는 원칙을 프로그램의 최상위 계층까지 지켜나간다면, 그 마지막 계층은 사용자를 위한 프로그래밍 언어가 될겁니다. 이런 사고방식 자체가 Lisp에서 기원했기 때문에, Lisp가 확장성 있는 소프트웨어를 작성하는데 최적의 언어라고 말할 수 있습니다. Gnu Emacs, Autocad, Interleaf를 아십니까? 1980년대에 Lisp를 확장 언어로 사용했던 가장 성공적인 프로그램들입니다.
아래에서 위로 작업하는 것은 재사용 가능한 소프트웨어를 얻어내는 가장 좋은 방법이기도 합니다. 재사용 가능한 소프트웨어의 핵심은 특정한 것에서 일반적인 것을 분리해내는 것에 있습니다. 그리고 아래에서 위로 프로그래밍하게 되면 자연히 그런 분리를 만들어내게 됩니다. 여러분의 모든 노력을 단 하나의 통짜 어플리케이션을 작성하는데 들이붓는 대신에, 그 노력의 일부를 언어를 만들어내는데 쓰고, 나머지는 그 위에 올라가는 상대적으로 더 작은 어플리케이션을 만드는데 사용할 수 있습니다. 하위 계층이 어플리케이션을 만드는데 필요한 언어를 구성하게 되는겁니다. 프로그래밍 언어보다 더 재사용성이 높은게 있나요?
Lisp는 더 복잡한 프로그램을 만들 수 있게 할 뿐만 아니라, 더 빨리 작성할 수 있게 합니다. Lisp 프로그램은 짧은 경향이 있는데요. Frederick Brooks가 지적했듯이, 프로그램을 작성하는 시간은 대체로 프로그램의 길이에 비례합니다. Lisp 프로그램은 작성하는데 시간이 덜 든다는 소리죠. 여기에 Lisp의 동적인 속성이 더해지면 엄청나게 작성 속도가 빨라집니다. Lisp의 편집-컴파일-테스트 주기는 아주 짧기 때문에 프로그래밍이 실시간으로 이루어집니다.
강력한 추상화 능력과 동적인 개발 환경은 조직이 소프트웨어를 개발하는 방법을 바꿔놓을 수 있습니다. 고속 프로토타이핑은 Lisp와 함께 시작된 프로그래밍 방법입니다: Lisp에서는 스펙을 작성하는데 걸리는 시간보다 적은 시간을 들이면서도 프로토타입을 작성하는 것이 가능합니다. 게다가 이런 프로토타입은 충분히 추상적으로 만들 수 있기 때문에 영문으로 기술하는 것보다 더 나은 스펙을 작성할 수 있습니다. 그리고 Lisp를 사용하면 프로토타입에서 출시 가능한 소프트웨어로 부드럽게 옮겨가도록 만들 수 있습니다. 성능에 주의를 기울여서 코드를 작성하고 최신 컴파일러를 사용하면, Common Lisp로 작성된 프로그램도 다른 언어로 작성된 프로그램들만큼 빠르게 동작합니다.
Lisp를 충분히 알고 있지 못한 상태에서 보면, 지금까지 소개한 내용들이 뭔가 대단해 보이기는 하지만 별 쓸모없는 얘기로 들릴 수도 있습니다. Lisp가 객체 지향 프로그래밍을 초월하든, 프로그램을 아래에서 위로 작성하든, 실시간으로 프로그래밍을 하든, 그게 대체 뭔 상관이냐고요? 지금 당장은 의미가 잘 다가오지 않겠지만 Lisp의 특징들을 하나하나 배워가면서 동작하는 실제 프로그램들을 보게 되면, 경험이 쌓이면서 점점 명확하게 전체적인 윤곽을 볼 수 있을 것입니다.
새로운 접근법
이 책의 목적은 단지 Lisp 언어를 설명하는 것에 그치지 않습니다. Lisp가 가져다 줄 수 있는 새로운 프로그래밍 접근법에도 초점을 두고 있지요. 새로운 접근 방식은 여러분이 미래를 더 내다볼 수 있도록 해줍니다. 프로그래밍 환경이 점점 강력해지고, 언어가 더 강력한 추상화를 지원하게 되면서 Lisp 스타일로 프로그래밍하는 것이 점점 "계획한 다음 구현하기"라는 낡은 모델을 갈아치우고 있습니다.
오래된 프로그래밍 모델에서는 버그라는 것이 절대로 발생해서는 안 되는 것이었습니다. 아주 철저하게 스펙을 작성하고, 무지막지하게 공을 들여서, 한 번에 프로그램이 제대로 동작한다는 것을 보장한다는건데요. 이론적으로야 그럴듯하죠. 스펙이나 코드나 사람이 작성하는게 처음부터 완벽할 수 없습니다. 그렇다보니 실전에서는 계획한 다음에 구현하는 방법이 잘 안 먹힙니다.
OS/360 프로젝트의 관리자였던 Frederick Brooks는 이런 전통적인 접근법에 대해 정통했습니다. 그리고 전통적인 접근법을 따라 개발했던 프로젝트의 결과를 이렇게 말했습니다.:
그 당시 가장 성공적인 시스템 중 하나였다는 물건이 이 정도입니다.OS/360 사용자라면 누구라도 이 물건이 얼마나 형편없는지 금방 알아차릴 것이다.. 게다가, 제품은 늦게 출시되었고, 메모리는 계획했던 것보다 많이 먹었으며, 비용은 예산을 몇 배나 초과했다. 그나마도 첫번째 릴리즈 이후 몇 번의 릴리즈를 더 거칠 때까지 제대로 동작하지도 않았다.
낡은 모델은 사람의 한계를 간단히 무시해버립니다. 이 낡은 모델에서는 "스펙에는 심각한 결함이 없다, 스펙을 코드로 변환하는 것은 아주 간단한 일이다"라고 가정하는데요. 경험을 되돌아보면 아주 말도 안 되는 소리라는 것을 알 수 있죠. 차라리 처음부터 스펙이 잘못 만들어졌을 수도 있다고 가정하고, 코드에는 버그가 가득할거라고 생각하는 편이 더 안전할겁니다.
이게 새로운 프로그래밍 모델이 가정하는 것들입니다. 사람들이 실수를 하지 않을거라고 예상하는게 아니라, 실수로 인해 발생하는 비용을 최소화하려고 시도합니다. 실수로 인해 발생하는 비용은 그걸 고치는데 들어가는 시간이지요. 강력한 언어와 훌륭한 프로그래밍 환경이 있으면 이 비용은 엄청나게 줄일 수 있습니다. 이렇게 되면 계획에는 덜 의존하고 탐색에는 더 의존하는 프로그래밍 스타일로 바꿀 수 있습니다.
계획은 필요악입니다. 위험에 대한 반응이지요: 떠안고 있는 위험 부담이 클수록, 사전에 계획하는 것이 더 중요해집니다. 강력한 도구는 위험을 감소시킬 수 있고, 따라서 계획의 필요성을 줄입니다. 프로그램의 설계에는 가장 유용한 정보를 써먹을 수 있습니다: 실제로 구현해 본 경험 말입니다.
Lisp 스타일은 1960년대부터 이런 방향으로 발전해왔습니다. 옛날 방식대로라면 스펙 작성을 막 완료했을 시간에, 실제로 설계와 구현을 몇 번 반복하면서 프로토타입을 만들어낼 수 있습니다. 실제로 해보면서 문제가 될 수 있는 요소들을 꽤 많이 찾아낼 수 있기 때문에 설계 결함에 대해 크게 걱정할 필요가 없지요. 버그에 대해서도 별로 걱정할 필요가 없습니다. 함수형 스타일로 프로그램을 작성하면, 버그가 발생해도 아주 부분적으로만 영향을 주니까요. 아주 추상적인 언어를 사용하면 어떤 버그의 발생 가능성은 원천적으로 봉쇄되고 (이를테면 무효화된 포인터라든가), 다른 버그들은 프로그램 코드의 길이가 짧기 때문에 찾는데 그리 오래 걸리지 않습니다. 여기에 추가로 대화식 개발 환경을 가지고 있으면 편집, 컴파일, 테스트하는 오랜 주기를 견디지 않고도 즉각 버그를 제거할 수 있습니다.
Lisp 스타일이 이런 식으로 진화해 온 것은 그냥 이런 방식이 제대로 결과를 내줬기 때문입니다. 좀 이상하게 들릴 수도 있는데, 계획을 덜 하는게 더 좋은 설계를 만들어낸다는거죠. 기술의 역사를 보면 서로 비슷한 사건들을 접할 수 있는데, 15세기의 화법을 한 번 예로 들어보겠습니다. 유화가 인기를 끌기 전에는 화가들이 템페라 물감을 썼는데 이건 색을 혼합할 수도 없고 덧칠할 수도 없었습니다. 실수 한 번 했다간 처음부터 다시 그려야하기 때문에, 이 물감은 화가들을 보수적으로 만들었지요. 그런데 유화 물감이 발명되고 나서는 스타일에 일대 혁명이 일어났습니다. 순간적으로 떠오르는 것을 그냥 그려버려도 됩니다. 특히 초상화 같은 어려운 주제를 다룰 때에는 아주 결정적인 장점이라고 말할 수 있습니다.
새로운 착색제의 발견은 단순히 화가들의 삶을 더 편하게 해준 정도로 끝나지 않았습니다. 새롭고 대담한 회화 기법들을 선보일 수 있게 되었으니까요. Janson은 이렇게 기록하고 있습니다:
유화 물감이 없었다면, 플랑드르의 거장들이 이룩한 초현실성의 정복은 더 많이 제한될 수 밖에 없었을 것이다. 기술적인 관점으로만 보더라도 이들은 유화 물감을 화가의 필수 재료로 쓰이게 하였으므로 "현대 미술의 아버지"라고 부르기에 손색이 없다.
재료 자체만으로 따지자면 템페라가 유화 물감보다 덜 아름답다거나 한 것은 아닙니다. 그렇지만 유화 물감의 유연성이 상상의 폭을 넓혀준 것이지요 - 이것이 결정적인 요인이었습니다.
프로그래밍도 비슷한 변화를 겪고 있습니다. 변화를 주도하는 새로운 매체는 동적인 객체 지향 언어 - 한 마디로, Lisp - 라 할 수 있습니다. 물론 몇 년 후에는 모든 소프트웨어가 Lisp로 작성될 것이라고 이야기하는 것은 아닙니다. 템페라에서 유화 물감으로 넘어가는 과정도 하루 아침에 일어난 것은 아니죠. 처음에는 선도적인 일부 예술관에서만 인기있다가, 시간이 흐르자 템페라와 조합해서 쓰이는 양상을 보였습니다. 우리는 지금 전환기를 보고 있습니다. Lisp는 대학, 연구소, 그리고 선도적인 일부 회사에서만 사용되고 있습니다. 그렇지만 이와 동시에, Lisp에서 차용된 특징들은 급속하게 주류로 편입되고 있습니다: 간단히 몇 가지만 예로 들자면 대화식 프로그래밍 환경, 메모리 자동 관리, 실행 시간 타입 계산이 있겠네요.
더 강력한 도구는 탐색의 부담을 떠안아줍니다. 프로그래머에게는 아주 좋은 소식이지요. 어떻게 될지 모르는 프로젝트라도 시작할 수 있으니까요. 유화 물감의 사용이 확실히 이런 효과를 가지고 있었습니다. 유화 물감을 받아들인 시기 직후에는 미술의 황금기가 이어졌는데요. 프로그래밍 세계도 그렇게 될 조짐이 이미 보이고 있습니다.




최근 덧글