프로그래밍을 지적 활동으로 바라보며, 인간의 마음·프로그램의 집합·컴퓨터라는 세 초점을 통해 Lisp(특히 Scheme)와 대규모 시스템 조직화 기법의 의미를 성찰하는 서문.
교육자, 장군, 영양사, 심리학자, 부모는 프로그램을 짠다. 군대, 학생, 그리고 어떤 사회들은 프로그램되어 있다. 큰 문제에 대한 공격은 연속된 프로그램들의 사용을 수반하는데, 그 대부분은 진행 도중에 생겨난다. 이 프로그램들은 당면한 문제에만 특유해 보이는 이슈들로 가득하다. 프로그래밍을 그 자체로서의 지적 활동으로 이해하려면 컴퓨터 프로그래밍으로 눈을 돌려야 한다. 컴퓨터 프로그램을 읽고 쓰라 — 그것도 많이. 그 프로그램들이 무엇에 관한 것인지, 어떤 응용을 위한 것인지는 그다지 중요하지 않다. 중요한 것은 그것들이 얼마나 잘 수행하는지, 그리고 더 큰 프로그램을 만들어내는 과정에서 다른 프로그램들과 얼마나 매끄럽게 맞물리는지다. 프로그래머는 부분의 완전함(perfection)과 전체 묶음의 적절함(adequacy)을 동시에 추구해야 한다. 이 책에서 “프로그램”이라는 말은 디지털 컴퓨터에서 실행될 수 있도록 Lisp의 한 방언으로 작성된 프로그램을 만들고, 실행하고, 연구하는 데 초점을 둔다. Lisp를 사용함으로써 우리가 프로그래밍할 수 있는 것을 제한하는 것이 아니라, 오직 프로그램 기술을 위한 표기법만을 제한한다.
이 책의 주제와 우리가 맺는 관계는 세 가지 현상의 초점으로 우리를 이끈다. 인간의 마음, 컴퓨터 프로그램들의 집합, 그리고 컴퓨터. 모든 컴퓨터 프로그램은 마음속에서 부화한, 어떤 현실적 또는 정신적 과정의 모델이다. 인간의 경험과 사고에서 생겨나는 이런 과정들은 수가 방대하고, 세부가 정교하며, 어느 순간에도 부분적으로만 이해된다. 따라서 우리의 컴퓨터 프로그램이 이러한 과정들을 영구히 만족스럽게 모델링하는 일은 드물다. 그러므로 우리의 프로그램이 정성껏 손으로 만든 이산적인 기호들의 모음, 서로 맞물린 함수들의 모자이크라 할지라도, 그것들은 끊임없이 진화한다. 우리는 모델에 대한 인식이 깊어지고, 확장되고, 일반화됨에 따라 프로그램을 바꾸며, 마침내 그 모델이 우리가 씨름하는 또 다른 모델 속에서 준안정적(metastable) 자리를 얻을 때까지 그렇게 한다. 컴퓨터 프로그래밍에 수반되는 환희의 근원은, 프로그램으로 표현된 메커니즘이 마음속과 컴퓨터 위에서 끊임없이 펼쳐지고, 그것이 만들어내는 인식의 폭발에 있다. 예술이 우리의 꿈을 해석한다면, 컴퓨터는 프로그램이라는 모습으로 그것을 실행한다!
그 힘에도 불구하고, 컴퓨터는 가혹한 감독자다. 그 프로그램은 반드시 정확해야 하며, 우리가 말하고자 하는 바는 모든 세부에서 정확하게 말해져야 한다. 다른 모든 기호적 활동에서와 마찬가지로, 우리는 논증을 통해 프로그램의 진리를 확신하게 된다. Lisp 자체에도 의미론(또 하나의 모델이기도 하다)을 부여할 수 있고, 어떤 프로그램의 기능이 예컨대 술어 논리로 명세될 수 있다면, 논리의 증명 기법을 사용해 수용 가능한 정확성 논증을 만들 수 있다. 불행히도 프로그램이 크고 복잡해질수록(대개 그렇듯이), 명세 자체의 충분성, 일관성, 정확성도 의심의 여지가 생기므로, 큰 프로그램에는 완전한 형식적 정확성 논증이 거의 동반되지 않는다. 큰 프로그램은 작은 프로그램에서 자라나므로, 우리가 정확성을 확신하게 된 표준 프로그램 구조의 무기고를 개발하는 것이 중요하다 — 우리는 이를 관용구(idiom)라 부른다 — 그리고 검증된 가치가 있는 조직화 기법을 사용해 이를 더 큰 구조로 결합하는 법을 배워야 한다. 이러한 기법들은 이 책에서 자세히 다루며, 그것들을 이해하는 것은 프로그래밍이라는 프로메테우스적 기업에 참여하는 데 필수적이다. 무엇보다도, 강력한 조직화 기법을 발견하고 숙달하는 일은 크고 의미 있는 프로그램을 만들어내는 능력을 가속한다. 반대로, 큰 프로그램을 작성하는 일은 매우 고된 작업이므로, 우리는 큰 프로그램에 끼워 맞춰야 하는 기능과 세부의 덩어리를 줄이는 새로운 방법을 발명하도록 자극받는다.
프로그램과 달리, 컴퓨터는 물리 법칙을 따라야 한다. 빠르게 수행하려면 — 상태 변화당 몇 나노초 — 전자를 아주 짧은 거리(최대 1.5피트)만 이동시켜야 한다. 그처럼 좁은 공간에 빽빽하게 모인 방대한 수의 장치가 생성하는 열은 제거되어야 한다. 기능의 다양성과 장치의 밀도 사이에서 균형을 잡는 정교한 공학적 예술이 발전해 왔다. 어쨌든 하드웨어는 언제나 우리가 프로그래밍하고자 하는 수준보다 더 원시적인 수준에서 동작한다. 우리의 Lisp 프로그램을 “기계” 프로그램으로 변환하는 과정 자체도 우리가 프로그래밍하는 추상 모델들이다. 그것들을 연구하고 만들어내는 일은 임의의 모델을 프로그래밍하는 데 수반되는 조직화 프로그램들에 대해 많은 통찰을 준다. 물론 컴퓨터 자체도 그렇게 모델링될 수 있다. 생각해 보라. 가장 작은 물리적 스위칭 소자의 거동은 미분방정식으로 기술된 양자역학으로 모델링되며, 그 상세한 거동은 수치적 근사로 포착되어, …로 구성된 컴퓨터에서 실행되는 컴퓨터 프로그램으로 표현된다!
세 가지 초점을 별도로 식별하는 것은 단지 전술적 편의의 문제가 아니다. “결국 다 머릿속의 일”이라 해도, 이런 논리적 분리는 이 초점들 사이의 기호적 교류를 가속하며, 그 풍요로움과 생동감과 잠재력은 인간 경험에서 생명의 진화만이 능가한다. 최선의 경우에도 이 초점들 사이의 관계는 준안정적이다. 컴퓨터는 결코 충분히 크거나 빠르지 않다. 하드웨어 기술의 각 돌파구는 더 거대한 프로그래밍 기업, 새로운 조직화 원리, 그리고 추상 모델의 풍요로움을 불러온다. 모든 독자는 때때로 “무엇을 향해서, 무엇을 향해서?”라고 스스로에게 물어야 한다. 다만 너무 자주 묻지는 말라. 그렇지 않으면 프로그래밍의 즐거움을 쓰디쓴 철학의 변비로 바꾸어 놓고 말 테니.
우리가 작성하는 프로그램들 가운데 일부(하지만 결코 충분할 만큼 많지는 않다)는 정렬이나 수열의 최댓값 찾기, 소수 판정, 제곱근 구하기 같은 정확한 수학적 함수를 수행한다. 우리는 이런 프로그램을 알고리즘이라 부르며, 특히 실행 시간과 데이터 저장 요구량이라는 두 중요한 매개변수와 관련해 그것들의 최적 거동에 관해 많은 것이 알려져 있다. 프로그래머는 좋은 알고리즘과 관용구를 습득해야 한다. 어떤 프로그램들은 정밀한 명세를 거부하기도 하지만, 그 성능을 추정하고, 항상 개선하려는 시도를 하는 것은 프로그래머의 책임이다.
Lisp는 생존자다. 약 4분의 1세기 동안 사용되어 왔다. 현역 프로그래밍 언어 가운데 더 긴 생명을 가진 것은 Fortran뿐이다. 두 언어 모두 중요한 응용 분야의 프로그래밍 요구를 지탱해 왔다. Fortran은 과학 및 공학 계산을 위해, Lisp는 인공지능을 위해. 이 두 분야는 계속 중요하며, 그 프로그래머들은 각자의 언어에 매우 헌신적이어서 Lisp와 Fortran은 적어도 또 다른 4분의 1세기 동안 현역으로 남아 있을 가능성이 크다.
Lisp는 변한다. 이 텍스트에서 사용하는 Scheme 방언은 원래의 Lisp에서 진화했으며, 변수 바인딩에 대한 정적 스코핑, 함수가 함수를 값으로 돌려줄 수 있도록 허용하는 것 등 여러 중요한 점에서 후자와 다르다. 의미론적 구조에서 Scheme은 초기 Lisp들만큼이나 Algol 60과도 가깝다. 다시는 현역 언어가 되지 않을 Algol 60은 Scheme과 Pascal의 유전자 속에 살아 있다. 이 두 언어를 둘러싼 두 문화보다 더 다른 두 문화의 교환 화폐가 되는 언어를 찾기란 어려울 것이다. Pascal은 피라미드를 세우기 위한 것이다 — 군대가 무거운 블록을 밀어 넣어 자리에 고정시키는, 장엄하고 숨 막히게 인상적이며 정적인 구조물. Lisp는 유기체를 만들기 위한 것이다 — 분대가 단순한 유기체들의 요동치는 무수한 무리를 끼워 맞춰 자리를 잡아가는, 장엄하고 숨 막히게 인상적이며 동적인 구조물. 두 경우에 사용되는 조직화 원리는 동일하다. 단 한 가지, 극도로 중요한 차이만 제외하면. 개별 Lisp 프로그래머에게 위임되는 재량적이고 외부로 내보낼 수 있는 기능성(exportable functionality)은 Pascal 기업에서 발견되는 것보다 한 자릿수 이상(10배 이상) 더 크다. Lisp 프로그램은 그것을 낳은 응용을 넘어서는 유용성을 가진 함수들로 라이브러리를 부풀린다. Lisp의 토착 데이터 구조인 리스트(list)는 이러한 유용성의 성장에 크게 책임이 있다. 리스트의 단순한 구조와 자연스러운 적용 가능성은 놀라울 만큼 비특이적(nonidiosyncratic)인 함수들에 반영된다. Pascal에서는 선언 가능한 데이터 구조가 과도하게 많기 때문에 함수 내부의 전문화가 유도되며, 이는 느슨한 협력을 억제하고 불이익을 준다. 10개의 함수가 10개의 데이터 구조에 작동하게 하는 것보다, 100개의 함수가 하나의 데이터 구조에 작동하게 하는 편이 낫다. 그 결과 피라미드는 천 년 동안 변치 않은 채 서 있어야 하고, 유기체는 진화하거나 멸망해야 한다.
이 차이를 보여 주기 위해, 이 책의 소재와 연습문제 처리 방식을 Pascal을 사용하는 어떤 1학기용 교재와 비교해 보라. 이것이 MIT에서만 소화 가능한 텍스트이며, 그곳에서 발견되는 어떤 종(種)에게나 특이한 것이라는 착각에 빠지지 말라. 이것은 학생이 누구든, 어디에서 사용되든, Lisp로 프로그래밍을 진지하게 다루는 책이라면 반드시 그래야 하는 바로 그것이다.
이것이 인공지능을 위한 준비로 사용되는 대부분의 Lisp 책들과 달리, 프로그래밍에 관한 텍스트임을 유의하라. 결국, 연구 대상이 되는 시스템이 더 커질수록 소프트웨어 공학과 인공지능의 핵심 프로그래밍 관심사는 서로 합쳐지는 경향이 있다. 이것이 인공지능 바깥에서 Lisp에 대한 관심이 커지는 이유를 설명해 준다.
목표에서 기대할 수 있듯이, 인공지능 연구는 많은 중요한 프로그래밍 문제를 만들어낸다. 다른 프로그래밍 문화에서는 이 문제의 범람이 새로운 언어들을 낳는다. वास्तव상, 어떤 매우 큰 프로그래밍 과업에서든 유용한 조직화 원리는 언어의 발명을 통해 과업 모듈 내부의 교통(상호작용)을 통제하고 격리하는 것이다. 이런 언어들은 우리가 가장 자주 상호작용하는 시스템의 경계에 가까워질수록 덜 원시적인 형태가 되는 경향이 있다. 그 결과, 그런 시스템에는 복잡한 언어 처리 함수들이 여러 번 복제되어 들어 있다. Lisp는 문법과 의미론이 매우 단순해서, 파싱(parsing)을 초등적인 과제로 취급할 수 있다. 따라서 파싱 기술은 Lisp 프로그램에서 거의 역할을 하지 않으며, 언어 처리기를 구성하는 일은 큰 Lisp 시스템의 성장과 변화 속도를 좀처럼 방해하지 않는다. 마지막으로, 바로 이 문법과 의미론의 단순함 때문에 모든 Lisp 프로그래머가 짊어지는 부담과 자유가 생겨난다. 몇 줄을 넘는 규모의 Lisp 프로그램은 어느 것도 재량적 함수로 포화되지 않고서는 작성될 수 없다. 발명하고 끼워 맞춰라; 발작하고 재발명하라! 괄호의 둥지 안에 자신의 생각을 적어 내려가는 Lisp 프로그래머에게 건배를.
Alan J. Perlis
코네티컷주 뉴헤이븐