너드들의 복수

스타트업을 시작하고 싶으신가요? Y Combinator에서 자금을 지원받으세요.


2002년 5월

| "우리는 C++ 프로그래머들을 쫓고 있었다. 그들 중 많은 이들을 리스프에 절반쯤 끌어들이는 데 성공했다."

  • 자바 사양 공동 저자 가이 스틸

소프트웨어 비즈니스에는 뾰족한 머리의 학자들과 그에 못지않게 강력한 또 다른 세력인 뾰족한 머리의 상사들 사이에 끊임없는 투쟁이 존재합니다. 뾰족한 머리의 상사가 누구인지 다들 아시죠? 기술 업계에 종사하는 대부분의 사람들은 이 만화 캐릭터를 알아볼 뿐만 아니라, 그 캐릭터의 모델이 된 실제 인물이 회사에 있다는 것을 알고 있을 것입니다.

뾰족한 머리의 상사는 그 자체로는 흔하지만 함께 보기 드문 두 가지 특성을 기적적으로 결합합니다: (a) 기술에 대해 전혀 아는 바가 없고, (b) 기술에 대해 매우 강한 의견을 가지고 있다는 것입니다.

예를 들어, 소프트웨어 한 조각을 작성해야 한다고 가정해 봅시다. 뾰족한 머리의 상사는 이 소프트웨어가 어떻게 작동해야 하는지 전혀 모르고, 프로그래밍 언어를 구별하지도 못하지만, 당신이 어떤 언어로 작성해야 하는지는 알고 있습니다. 정확히 그렇습니다. 그는 당신이 자바로 작성해야 한다고 생각합니다.

그는 왜 그렇게 생각할까요? 뾰족한 머리의 상사의 뇌 속을 들여다봅시다. 그가 생각하는 것은 대략 이렇습니다. 자바는 표준입니다. 항상 언론에서 읽기 때문에 분명히 표준일 것입니다. 표준이므로, 자바를 사용해도 문제가 생기지 않을 것입니다. 그리고 그것은 항상 많은 자바 프로그래머가 있을 것이라는 의미이기도 합니다. 그러니 지금 저를 위해 일하는 프로그래머들이 미스터리하게도 항상 그렇듯이 그만두더라도, 저는 쉽게 그들을 대체할 수 있습니다.

음, 그렇게 비합리적으로 들리지는 않습니다. 하지만 이 모든 것은 한 가지 암묵적인 가정에 기반을 두고 있으며, 그 가정은 틀린 것으로 판명됩니다. 뾰족한 머리의 상사는 모든 프로그래밍 언어가 거의 동등하다고 믿습니다. 만약 그것이 사실이라면, 그의 생각은 정확할 것입니다. 언어가 모두 동등하다면, 물론 다른 모든 사람이 사용하는 언어를 사용해도 됩니다.

하지만 모든 언어가 동등하지는 않으며, 저는 그 차이점에 대해 자세히 설명하지 않고도 이를 증명할 수 있다고 생각합니다. 만약 1992년에 뾰족한 머리의 상사에게 소프트웨어를 어떤 언어로 작성해야 하는지 물었다면, 그는 오늘날과 마찬가지로 망설임 없이 대답했을 것입니다. 소프트웨어는 C++로 작성되어야 한다고요. 하지만 언어가 모두 동등하다면, 뾰족한 머리의 상사의 의견이 왜 바뀌어야 할까요? 사실, 자바 개발자들은 왜 굳이 새로운 언어를 만들려고 했을까요?

아마도 새로운 언어를 만든다면, 그것은 기존의 것보다 어떤 면에서 더 낫다고 생각하기 때문일 것입니다. 실제로 고슬링은 첫 자바 백서에서 자바가 C++의 일부 문제를 해결하기 위해 설계되었다고 명확히 밝혔습니다. 그러니 결론은 이렇습니다: 언어는 모두 동등하지 않습니다. 뾰족한 머리의 상사의 뇌를 통해 자바로, 그리고 자바의 역사를 거슬러 그 기원까지 따라가 보면, 당신이 처음 가졌던 가정과 모순되는 아이디어를 붙들게 될 것입니다.

그렇다면 누가 옳을까요? 제임스 고슬링일까요, 아니면 뾰족한 머리의 상사일까요? 놀랄 것도 없이 고슬링이 옳습니다. 어떤 언어는 특정 문제에 대해 다른 언어보다 좋습니다. 그리고 아시다시피, 이것은 몇 가지 흥미로운 질문을 제기합니다. 자바는 특정 문제에 대해 C++보다 더 낫도록 설계되었습니다. 어떤 문제들일까요? 자바가 더 나은 경우는 언제이고 C++이 더 나은 경우는 언제일까요? 이 둘보다 다른 언어가 더 나은 상황도 있을까요?

이 질문을 고려하기 시작하면, 당신은 진정한 판도라의 상자를 열게 됩니다. 뾰족한 머리의 상사가 이 문제를 그 복잡성 전체로 생각해야 한다면, 그의 뇌는 폭발할 것입니다. 그가 모든 언어를 동등하다고 여기는 한, 그가 해야 할 일은 가장 모멘텀이 있어 보이는 것을 선택하는 것뿐이며, 그것이 기술보다는 유행의 문제이므로 그조차도 아마 정답을 찾을 수 있을 것입니다. 하지만 언어가 다양하다면, 그는 갑자기 두 가지 동시 방정식을 풀어야 합니다. 그가 전혀 모르는 두 가지, 즉 그가 해결해야 할 문제에 대한 약 20여 개의 주요 언어의 상대적 적합성과 각 언어에 대한 프로그래머, 라이브러리 등을 찾을 확률 사이에서 최적의 균형을 찾으려고 노력해야 합니다. 만약 문 저편에 이런 것이 있다면, 뾰족한 머리의 상사가 그 문을 열고 싶어 하지 않는 것은 놀랄 일이 아닙니다.

모든 프로그래밍 언어가 동등하다고 믿는 것의 단점은 그것이 사실이 아니라는 것입니다. 하지만 장점은 당신의 삶을 훨씬 더 단순하게 만든다는 것입니다. 그리고 저는 그것이 이 아이디어가 널리 퍼진 주된 이유라고 생각합니다. 그것은 편안한 아이디어입니다.

우리는 자바가 멋지고 새로운 프로그래밍 언어이기 때문에 꽤 좋다고 알고 있습니다. 정말 그럴까요? 프로그래밍 언어의 세계를 멀리서 보면, 자바가 최신 유행처럼 보입니다. (충분히 멀리서 보면, 선(Sun)이 돈을 지불한 크고 번쩍이는 광고판만 보일 뿐입니다.) 하지만 이 세계를 가까이서 보면, 멋짐에도 정도가 있다는 것을 알게 됩니다. 해커 서브컬처 내에서는 자바보다 훨씬 더 멋지다고 여겨지는 펄(Perl)이라는 또 다른 언어가 있습니다. 예를 들어, 슬래시닷(Slashdot)은 펄로 생성됩니다. 저는 그 사람들이 자바 서버 페이지를 사용하고 있다고 생각하지 않습니다. 하지만 펄 사용자들을 경시하는 경향이 있는 파이썬(Python)이라는 또 다른, 더 새로운 언어가 있으며, 더 많은 언어들이 대기하고 있습니다.

이 언어들을 순서대로, 자바, 펄, 파이썬을 살펴보면 흥미로운 패턴을 발견할 수 있습니다. 적어도 당신이 리스프 해커라면 이 패턴을 알아차릴 것입니다. 각 언어는 점진적으로 리스프와 더 유사해집니다. 파이썬은 많은 리스프 해커들이 실수라고 여기는 기능들조차 복사합니다. 간단한 리스프 프로그램을 파이썬으로 한 줄씩 번역할 수 있을 정도입니다. 2002년이고, 프로그래밍 언어들은 거의 1958년 수준에 도달했습니다.

수학을 따라잡다

제가 의미하는 바는 리스프가 1958년 존 매카시(John McCarthy)에 의해 처음 발견되었고, 인기 있는 프로그래밍 언어들은 이제야 그가 당시 개발했던 아이디어들을 따라잡고 있다는 것입니다.

자, 어떻게 그럴 수 있을까요? 컴퓨터 기술은 매우 빠르게 변하는 것이 아닌가요? 제 말은, 1958년에는 컴퓨터가 손목시계 정도의 처리 능력을 가진 냉장고 크기의 거대 장치였다는 것입니다. 그렇게 오래된 기술이 최신 개발보다 우수하기는커녕 어떻게 관련성이 있을 수 있을까요?

제가 말씀드리겠습니다. 그것은 리스프가 원래 프로그래밍 언어로 설계된 것이 아니었기 때문입니다. 적어도 오늘날 우리가 의미하는 방식으로는 말이죠. 우리가 프로그래밍 언어라고 말하는 것은 컴퓨터에게 무엇을 해야 할지 지시하는 데 사용하는 것입니다. 매카시는 결국 이런 의미의 프로그래밍 언어를 개발할 의도가 있었지만, 우리가 실제로 얻게 된 리스프는 그가 이론적 연습으로 수행했던 별개의 것에 기반을 두었습니다--튜링 머신(Turing Machine)에 대한 더 편리한 대안을 정의하려는 노력이었습니다. 매카시가 나중에 말했듯이,

리스프가 튜링 머신보다 더 깔끔하다는 것을 보여주는 또 다른 방법은 범용 리스프 함수를 작성하여 그것이 범용 튜링 머신의 설명보다 더 간결하고 이해하기 쉽다는 것을 보여주는 것이었다. 이것이 바로 리스프 표현식의 값을 계산하는 리스프 함수 eval...였다. _eval_을 작성하려면 리스프 함수를 리스프 데이터로 표현하는 표기법을 발명해야 했고, 그러한 표기법은 실제로 리스프 프로그램을 표현하는 데 사용될 것이라는 생각 없이 논문의 목적을 위해 고안되었다.

그다음 일어난 일은 1958년 말 어느 시점에 매카시의 대학원생 중 한 명인 스티브 러셀(Steve Russell)이 이 eval 정의를 보고 그것을 기계어로 번역하면 리스프 인터프리터가 될 것이라는 사실을 깨달았다는 것입니다.

이것은 당시 큰 놀라움이었습니다. 매카시가 나중에 인터뷰에서 이에 대해 말한 내용은 다음과 같습니다:

스티브 러셀이 '이 _eval_을 프로그래밍해 보는 게 어때요?'라고 말했고, 저는 그에게 '하하, 이론과 실제를 혼동하는군. 이 _eval_은 읽기 위한 것이지 계산하기 위한 것이 아니야.'라고 말했습니다. 하지만 그는 계속해서 그것을 해냈습니다. 즉, 그는 제 논문의 _eval_을 [IBM] 704 기계 코드로 컴파일하고 버그를 수정하여, 그것을 리스프 인터프리터로 광고했는데, 분명히 그랬습니다. 그래서 그 시점에 리스프는 본질적으로 오늘날과 같은 형태를 갖게 되었습니다....

갑자기, 몇 주 만에 매카시는 자신의 이론적 연습이 실제 프로그래밍 언어로 변모했음을 발견했습니다--그가 의도했던 것보다 훨씬 더 강력한 언어로 말이죠.

그러므로 이 1950년대 언어가 구식이 아닌 이유에 대한 짧은 설명은 그것이 기술이 아니라 수학이었고, 수학은 구식이 되지 않기 때문입니다. 리스프를 비교할 올바른 대상은 1950년대 하드웨어가 아니라, 예를 들어 1960년에 발견되어 여전히 가장 빠른 범용 정렬 알고리즘인 퀵소트(Quicksort) 알고리즘입니다.

1950년대부터 살아남은 또 다른 언어가 있는데, 바로 포트란(Fortran)입니다. 이 언어는 언어 설계에 대한 정반대의 접근 방식을 나타냅니다. 리스프는 예상치 못하게 프로그래밍 언어로 변모한 이론의 한 조각이었습니다. 포트란은 의도적으로 프로그래밍 언어로 개발되었지만, 지금 우리가 보기에는 매우 저수준의 언어였습니다.

1956년에 개발된 언어인 포트란 I은 오늘날의 포트란과는 매우 다른 종류였습니다. 포트란 I는 거의 수학이 가미된 어셈블리 언어였습니다. 어떤 면에서는 더 최근의 어셈블리 언어보다 덜 강력했습니다; 예를 들어, 서브루틴이 없고 오직 분기만 있었습니다. 오늘날의 포트란은 이제 포트란 I보다 리스프에 더 가깝다고 주장할 수 있습니다.

리스프와 포트란은 두 개의 별개 진화 트리의 줄기였습니다. 하나는 수학에 뿌리를 두었고, 다른 하나는 기계 아키텍처에 뿌리를 두었습니다. 이 두 트리는 그 이후로 계속 수렴해 왔습니다. 리스프는 처음부터 강력했고, 다음 20년 동안 빨라졌습니다. 소위 주류 언어들은 처음에는 빨랐고, 다음 40년 동안 점진적으로 더 강력해져서, 이제 가장 발전된 언어들은 리스프에 상당히 가깝습니다. 가깝지만, 여전히 몇 가지가 빠져 있습니다....

리스프를 다르게 만든 것

리스프가 처음 개발되었을 때, 아홉 가지 새로운 아이디어를 담고 있었습니다. 이 중 일부는 이제 당연하게 여겨지고, 다른 일부는 더 발전된 언어에서만 볼 수 있으며, 두 가지는 여전히 리스프에만 고유합니다. 아홉 가지 아이디어는 주류에 의해 채택된 순서대로 다음과 같습니다,

  1. 조건문. 조건문은 if-then-else 구조입니다. 우리는 이제 이것들을 당연하게 여기지만, 포트란 I에는 없었습니다. 그것은 오직 기본 기계 명령에 밀접하게 기반한 조건부 goto만 가지고 있었습니다.

  2. 함수 타입. 리스프에서 함수는 정수나 문자열처럼 데이터 타입입니다. 함수는 리터럴 표현을 가지며, 변수에 저장될 수 있고, 인수로 전달될 수 있습니다.

  3. 재귀. 리스프는 재귀를 지원한 최초의 프로그래밍 언어였습니다.

  4. 동적 타이핑. 리스프에서 모든 변수는 사실상 포인터입니다. 타입은 변수가 아니라 값에 있으며, 변수를 할당하거나 바인딩하는 것은 포인터 자체를 복사하는 것을 의미하며, 포인터가 가리키는 것을 복사하는 것이 아닙니다.

  5. 가비지 컬렉션.

  6. 표현식으로 구성된 프로그램. 리스프 프로그램은 표현식의 트리이며, 각 표현식은 값을 반환합니다. 이는 표현식과 문장을 구별하는 포트란 및 대부분의 후속 언어와 대조됩니다.

포트란 I에서 이러한 구별을 두는 것은 자연스러웠습니다. 왜냐하면 문장을 중첩할 수 없었기 때문입니다. 따라서 수학이 작동하려면 표현식이 필요했지만, 다른 어떤 것도 값을 반환하게 할 필요가 없었습니다. 왜냐하면 그 값을 기다리는 것이 있을 수 없었기 때문입니다.

이러한 제약은 블록 구조 언어의 등장과 함께 사라졌지만, 그때는 이미 너무 늦었습니다. 표현식과 문장 사이의 구별은 뿌리 깊게 자리 잡았습니다. 그것은 포트란에서 알골(Algol)로, 그리고 다시 그들의 후손들에게 퍼져나갔습니다.

  1. 심볼 타입. 심볼은 해시 테이블에 저장된 문자열에 대한 사실상의 포인터입니다. 따라서 각 문자를 비교하는 대신 포인터를 비교하여 동등성을 테스트할 수 있습니다.

  2. 심볼과 상수의 트리를 사용하는 코드 표기법.

  3. 항상 전체 언어가 존재함. 읽기 시간, 컴파일 시간, 런타임 사이에 실질적인 구별이 없습니다. 읽는 동안 코드를 컴파일하거나 실행할 수 있고, 컴파일하는 동안 코드를 읽거나 실행할 수 있으며, 런타임에 코드를 읽거나 컴파일할 수 있습니다.

읽기 시간에 코드를 실행하면 사용자가 리스프의 구문을 재프로그래밍할 수 있습니다; 컴파일 시간에 코드를 실행하는 것은 매크로의 기반입니다; 런타임에 컴파일하는 것은 이맥스(Emacs)와 같은 프로그램에서 리스프가 확장 언어로 사용되는 기반입니다; 그리고 런타임에 읽는 것은 프로그램들이 s-표현식(s-expressions)을 사용하여 통신할 수 있게 하는데, 이는 최근 XML로 재발명된 아이디어입니다.

리스프가 처음 등장했을 때, 이 아이디어들은 1950년대 후반에 사용 가능했던 하드웨어에 의해 크게 좌우되던 일반적인 프로그래밍 관행과는 거리가 멀었습니다. 시간이 지남에 따라, 일련의 인기 있는 언어들에 구현된 기본 언어는 점진적으로 리스프를 향해 진화했습니다. 아이디어 1-5는 이제 널리 퍼져 있습니다. 6번은 주류에서 나타나기 시작했습니다. 파이썬은 7번의 형태를 가지고 있지만, 이에 대한 구문은 없는 것 같습니다.

8번 아이디어에 관해서는, 이것이 가장 흥미로울 수 있습니다. 8번과 9번 아이디어는 스티브 러셀이 매카시가 구현될 의도가 전혀 없었던 것을 구현했기 때문에 우연히 리스프의 일부가 되었습니다. 하지만 이 아이디어들은 리스프의 이상한 외형과 가장 독특한 특징 모두에 책임이 있는 것으로 밝혀졌습니다. 리스프가 이상하게 보이는 것은 이상한 구문을 가지고 있기 때문이라기보다는 구문이 없기 때문입니다; 다른 언어가 파싱될 때 뒤에서 만들어지는 파스 트리(parse trees)에 프로그램을 직접 표현하며, 이 트리들은 리스프 데이터 구조인 리스트로 구성됩니다.

언어를 자체 데이터 구조로 표현하는 것은 매우 강력한 기능으로 밝혀졌습니다. 8번과 9번 아이디어를 합치면 프로그램을 작성하는 프로그램을 작성할 수 있다는 의미가 됩니다. 이는 기이한 아이디어처럼 들릴 수 있지만, 리스프에서는 일상적인 일입니다. 이를 수행하는 가장 일반적인 방법은 _매크로_라고 불리는 것을 사용하는 것입니다.

리스프에서 "매크로"라는 용어는 다른 언어에서 의미하는 바와 다릅니다. 리스프 매크로는 약어부터 새로운 언어의 컴파일러까지 무엇이든 될 수 있습니다. 리스프를 진정으로 이해하고 싶거나 단순히 프로그래밍 시야를 넓히고 싶다면, 매크로에 대해 더 알아보는 것이 좋습니다.

매크로(리스프 의미에서)는 제가 아는 한 여전히 리스프에만 고유합니다. 이는 부분적으로 매크로를 가지려면 언어를 리스프만큼 이상하게 만들어야 할 가능성이 높기 때문입니다. 또한, 만약 당신이 그 마지막 힘의 증분을 추가한다면, 더 이상 새로운 언어를 발명했다고 주장할 수 없고, 단지 리스프의 새로운 방언을 발명했다고만 주장할 수 있기 때문일 수도 있습니다.

이것은 주로 농담으로 언급하는 것이지만, 꽤 사실입니다. 만약 당신이 car, cdr, cons, quote, cond, atom, eq를 가지고 있고 리스트로 표현된 함수 표기법을 가진 언어를 정의한다면, 그 언어로 리스프의 나머지 모든 것을 구축할 수 있습니다. 사실 그것이 리스프의 정의적인 특성입니다: 매카시가 리스프에 현재의 형태를 부여한 것은 바로 이것을 가능하게 하기 위함이었습니다.

언어가 중요한 곳

그렇다면 리스프가 주류 언어들이 점근적으로 접근하는 일종의 한계를 나타낸다고 가정해 봅시다--그것이 실제로 소프트웨어를 작성하는 데 리스프를 사용해야 한다는 의미일까요? 덜 강력한 언어를 사용함으로써 얼마나 손해를 볼까요? 때로는 혁신의 최전선에 있지 않는 것이 더 현명하지 않을까요? 그리고 인기는 어느 정도 그 자체로 정당화되지 않을까요? 예를 들어, 뾰족한 머리의 상사가 프로그래머를 쉽게 고용할 수 있는 언어를 사용하고 싶어 하는 것이 옳지 않을까요?

물론 프로그래밍 언어 선택이 크게 중요하지 않은 프로젝트도 있습니다. 일반적으로 애플리케이션이 까다로울수록 강력한 언어를 사용함으로써 더 많은 이점을 얻을 수 있습니다. 하지만 전혀 까다롭지 않은 프로젝트도 많습니다. 대부분의 프로그래밍은 작은 '접착제' 프로그램을 작성하는 것으로 구성되며, 이러한 작은 프로그램에는 이미 익숙하고 필요한 작업을 위한 좋은 라이브러리가 있는 어떤 언어든 사용할 수 있습니다. 만약 한 윈도우 앱에서 다른 앱으로 데이터를 전달하기만 하면 된다면, 물론 비주얼 베이직(Visual Basic)을 사용하세요.

리스프로도 작은 '접착제' 프로그램을 작성할 수 있지만 (저는 데스크톱 계산기로 사용합니다), 리스프와 같은 언어의 가장 큰 장점은 스펙트럼의 다른 끝, 즉 치열한 경쟁 속에서 어려운 문제를 해결하기 위해 정교한 프로그램을 작성해야 하는 경우입니다. 좋은 예는 ITA 소프트웨어(ITA Software)가 오르비츠(Orbitz)에 라이선스하는 항공권 요금 검색 프로그램입니다. 이들은 이미 트래블로시티(Travelocity)와 익스피디아(Expedia)라는 두 개의 크고 뿌리 깊은 경쟁자가 지배하는 시장에 진입하여, 기술적으로 그들을 압도한 것으로 보입니다.

ITA 애플리케이션의 핵심은 20만 줄의 커먼 리스프(Common Lisp) 프로그램으로, 경쟁사들보다 훨씬 더 많은 가능성을 검색합니다. 경쟁사들은 여전히 메인프레임 시대의 프로그래밍 기술을 사용하고 있는 것으로 보입니다. (물론 ITA도 어떤 의미에서는 메인프레임 시대의 프로그래밍 언어를 사용하고 있습니다.) 저는 ITA의 코드를 본 적이 없지만, 그들의 최고 해커 중 한 명에 따르면 그들은 많은 매크로를 사용한다고 하며, 저는 그 말을 듣고 놀라지 않았습니다.

구심력

저는 흔치 않은 기술을 사용하는 데 비용이 들지 않는다고 말하는 것이 아닙니다. 뾰족한 머리의 상사가 이것을 걱정하는 것이 완전히 틀린 것은 아닙니다. 하지만 그는 위험을 이해하지 못하기 때문에 그것을 과장하는 경향이 있습니다.

덜 흔한 언어를 사용함으로써 발생할 수 있는 세 가지 문제를 생각할 수 있습니다. 당신의 프로그램이 다른 언어로 작성된 프로그램과 잘 작동하지 않을 수 있습니다. 사용할 수 있는 라이브러리가 더 적을 수 있습니다. 그리고 프로그래머를 고용하는 데 어려움을 겪을 수 있습니다.

이들 각각이 얼마나 큰 문제일까요? 첫 번째 문제의 중요성은 전체 시스템을 제어할 수 있는지 여부에 따라 달라집니다. 만약 버그가 많고 폐쇄적인 운영 체제(이름은 언급하지 않겠습니다) 위에서 원격 사용자의 기계에서 실행되어야 하는 소프트웨어를 작성한다면, OS와 동일한 언어로 애플리케이션을 작성하는 것이 이점이 있을 수 있습니다. 하지만 ITA가 그러하듯이, 전체 시스템을 제어하고 모든 부분의 소스 코드를 가지고 있다면, 원하는 어떤 언어든 사용할 수 있습니다. 어떤 비호환성이 발생하더라도 직접 수정할 수 있습니다.

서버 기반 애플리케이션에서는 가장 진보된 기술을 사용할 수 있으며, 저는 이것이 조나단 에릭슨(Jonathan Erickson)이 "프로그래밍 언어 르네상스"라고 부르는 현상의 주된 원인이라고 생각합니다. 이것이 바로 우리가 펄과 파이썬 같은 새로운 언어에 대해 듣게 되는 이유입니다. 사람들이 이 언어들을 윈도우 앱을 작성하는 데 사용하기 때문이 아니라, 서버에서 사용하기 때문입니다. 그리고 소프트웨어가 데스크톱에서 벗어나 서버로 이동함에 따라 (마이크로소프트조차도 체념한 미래), 중간 수준의 기술을 사용해야 한다는 압력은 점점 줄어들 것입니다.

라이브러리에 관해서는, 그 중요성 또한 애플리케이션에 따라 달라집니다. 덜 까다로운 문제의 경우, 라이브러리의 가용성이 언어의 본질적인 힘보다 더 중요할 수 있습니다. 손익분기점은 어디일까요? 정확히 말하기는 어렵지만, 어디든 애플리케이션이라고 부를 만한 것에는 미치지 못할 것입니다. 만약 어떤 회사가 스스로를 소프트웨어 비즈니스에 있다고 생각하고, 그들의 제품 중 하나가 될 애플리케이션을 작성하고 있다면, 아마도 여러 해커가 참여하고 작성하는 데 최소 6개월이 걸릴 것입니다. 그 정도 규모의 프로젝트에서는 강력한 언어가 기존 라이브러리의 편리함보다 더 중요해지기 시작할 것입니다.

뾰족한 머리의 상사의 세 번째 걱정, 즉 프로그래머 고용의 어려움은 저는 엉뚱한 이야기라고 생각합니다. 결국 몇 명의 해커를 고용해야 할까요? 이제 우리는 소프트웨어가 10명 미만의 팀에 의해 가장 잘 개발된다는 것을 모두 알고 있을 것입니다. 그리고 누구든 들어본 적 있는 어떤 언어에 대해서도 그 정도 규모의 해커를 고용하는 데 어려움을 겪어서는 안 됩니다. 만약 리스프 해커 10명을 찾을 수 없다면, 당신의 회사는 소프트웨어 개발에 적합하지 않은 도시에 기반을 두고 있을 가능성이 높습니다.

사실, 더 강력한 언어를 선택하는 것은 필요한 팀의 규모를 줄일 가능성이 높습니다. 왜냐하면 (a) 더 강력한 언어를 사용하면 더 많은 해커가 필요하지 않을 것이고, (b) 더 발전된 언어로 작업하는 해커들은 더 똑똑할 가능성이 높기 때문입니다.

저는 당신이 "표준" 기술로 인식되는 것을 사용하라는 많은 압력을 받지 않을 것이라고 말하는 것이 아닙니다. 비아웹(Viaweb, 현재 야후 스토어)에서 우리는 리스프를 사용하여 벤처 캐피탈(VC)과 잠재적 인수자들 사이에서 눈살을 찌푸리게 했습니다. 하지만 우리는 또한 선(Sun)과 같은 "산업용" 서버 대신 일반 인텔(Intel) 박스를 서버로 사용하고, 실제 상업용 OS인 윈도우 NT(Windows NT) 대신 당시 잘 알려지지 않은 오픈 소스 유닉스 변형인 FreeBSD를 사용하고, 이제 아무도 기억하지 못하는 SET이라는 전자상거래 표준을 무시하는 등 여러 면에서 눈살을 찌푸리게 했습니다.

당신은 정장 입은 사람들이 당신을 위해 기술적 결정을 내리도록 내버려 둘 수 없습니다. 우리가 리스프를 사용했다는 것이 일부 잠재적 인수자들을 놀라게 했을까요? 일부는 약간 그랬지만, 만약 우리가 리스프를 사용하지 않았다면, 그들이 우리를 사고 싶어 하게 만든 소프트웨어를 작성할 수 없었을 것입니다. 그들에게는 이상 현상처럼 보였던 것이 사실은 원인과 결과였습니다.

스타트업을 시작한다면, 벤처 캐피탈이나 잠재적 인수자들을 기쁘게 하기 위해 제품을 설계하지 마세요. 사용자들을 기쁘게 하기 위해 제품을 설계하세요. 사용자를 사로잡으면, 다른 모든 것은 따라올 것입니다. 그리고 그렇지 못하면, 당신의 기술 선택이 얼마나 편안하게 정통적이었는지 아무도 신경 쓰지 않을 것입니다.

평균이 되는 비용

덜 강력한 언어를 사용함으로써 얼마나 손해를 볼까요? 실제로 이에 대한 몇 가지 데이터가 있습니다.

가장 편리한 힘의 척도는 아마도 코드 크기일 것입니다. 고수준 언어의 요점은 더 큰 추상화를 제공하는 것입니다--말하자면 더 큰 벽돌을 제공하여, 주어진 크기의 벽을 쌓는 데 필요한 벽돌의 수가 줄어듭니다. 따라서 언어가 강력할수록 프로그램은 더 짧아집니다 (물론 단순히 문자 수가 아니라, 개별 요소 수에서).

더 강력한 언어가 어떻게 더 짧은 프로그램을 작성할 수 있게 할까요? 언어가 허용한다면 사용할 수 있는 한 가지 기술은 상향식 프로그래밍이라고 불리는 것입니다. 기본 언어로 단순히 애플리케이션을 작성하는 대신, 기본 언어 위에 당신과 같은 프로그램을 작성하기 위한 언어를 구축한 다음, 그 언어로 프로그램을 작성합니다. 결합된 코드는 전체 프로그램을 기본 언어로 작성했을 때보다 훨씬 짧을 수 있습니다--실제로 대부분의 압축 알고리즘이 이런 식으로 작동합니다. 상향식 프로그램은 수정하기도 더 쉬울 것입니다. 왜냐하면 많은 경우 언어 계층이 전혀 변경될 필요가 없기 때문입니다.

코드 크기는 중요합니다. 프로그램을 작성하는 데 걸리는 시간이 주로 그 길이에 달려 있기 때문입니다. 만약 당신의 프로그램이 다른 언어로 세 배 더 길어진다면, 작성하는 데 세 배 더 많은 시간이 걸릴 것입니다--그리고 더 많은 사람을 고용한다고 해서 이를 해결할 수는 없습니다. 왜냐하면 특정 규모를 넘어서면 신규 고용은 실제로 순손실이기 때문입니다. 프레드 브룩스(Fred Brooks)는 그의 유명한 책 _맨먼스 미신(The Mythical Man-Month)_에서 이 현상을 설명했으며, 제가 본 모든 것은 그의 말을 확인하는 경향이 있었습니다.

그렇다면 리스프로 프로그램을 작성하면 얼마나 더 짧아질까요? 예를 들어 리스프 대 C에 대해 제가 들은 대부분의 수치는 약 7-10배였습니다. 하지만 뉴 아키텍트(New Architect) 잡지의 ITA에 대한 최근 기사에서는 "리스프 한 줄이 C 20줄을 대체할 수 있다"고 말했으며, 이 기사는 ITA 사장의 인용문으로 가득했기 때문에 저는 그들이 이 수치를 ITA로부터 얻었다고 가정합니다. 그렇다면 우리는 이 수치를 신뢰할 수 있습니다; ITA의 소프트웨어에는 리스프뿐만 아니라 많은 C와 C++도 포함되어 있으므로, 그들은 경험을 바탕으로 말하고 있는 것입니다.

제 생각에는 이 배율이 일정하지도 않습니다. 더 어려운 문제에 직면할 때와 더 똑똑한 프로그래머가 있을 때 증가한다고 생각합니다. 정말 유능한 해커는 더 좋은 도구에서 더 많은 것을 짜낼 수 있습니다.

어쨌든 곡선 상의 한 데이터 포인트로서, 만약 당신이 ITA와 경쟁하고 소프트웨어를 C로 작성하기로 선택한다면, 그들은 당신보다 20배 빠르게 소프트웨어를 개발할 수 있을 것입니다. 당신이 새로운 기능에 1년을 보낸다면, 그들은 3주도 안 되어 그것을 복제할 수 있을 것입니다. 반면에 그들이 새로운 것을 개발하는 데 단 3개월을 보낸다면, 당신도 그것을 갖게 되기까지는 _5년_이 걸릴 것입니다.

그리고 아시나요? 그것은 최상의 시나리오입니다. 코드 크기 비율에 대해 이야기할 때, 당신은 암묵적으로 약한 언어로도 실제로 프로그램을 작성할 수 있다고 가정하고 있습니다. 하지만 사실 프로그래머가 할 수 있는 일에는 한계가 있습니다. 너무 저수준의 언어로 어려운 문제를 해결하려고 한다면, 한 번에 머릿속에 담아두기에는 너무 많은 것이 있는 지점에 도달하게 됩니다.

그래서 제가 ITA의 가상의 경쟁사가 ITA가 리스프로 3개월 만에 작성할 수 있는 것을 복제하는 데 5년이 걸릴 것이라고 말할 때, 그것은 아무 문제 없이 진행될 경우 5년이라는 의미입니다. 사실, 대부분의 회사에서 일이 진행되는 방식으로는 5년이 걸릴 개발 프로젝트는 아예 완료되지 않을 가능성이 높습니다.

이것이 극단적인 경우라는 것을 인정합니다. ITA의 해커들은 비정상적으로 똑똑해 보이며, C는 상당히 저수준 언어입니다. 하지만 경쟁 시장에서는 2~3배의 차이만으로도 당신이 항상 뒤처질 것이라는 것을 보장하기에 충분할 것입니다.

비결

이것은 뾰족한 머리의 상사가 생각조차 하고 싶어 하지 않는 가능성입니다. 그래서 대부분은 생각하지 않습니다. 왜냐하면, 결국 뾰족한 머리의 상사는 아무도 자신의 잘못임을 증명할 수 없는 한, 회사가 망하더라도 개의치 않기 때문입니다. 그 개인에게 가장 안전한 계획은 무리의 중심에 바싹 붙어 있는 것입니다.

대기업 내에서 이러한 접근 방식을 설명하는 데 사용되는 문구는 "업계 모범 사례(industry best practice)"입니다. 그 목적은 뾰족한 머리의 상사를 책임으로부터 보호하는 것입니다: 그가 "업계 모범 사례"인 것을 선택했고 회사가 손실을 본다면, 그를 비난할 수 없습니다. 그가 선택한 것이 아니라, 업계가 선택한 것이니까요.

저는 이 용어가 원래 회계 방법 등을 설명하는 데 사용되었다고 생각합니다. 대략적으로 그 의미는 _이상한 짓을 하지 마라_는 것입니다. 그리고 회계에서는 그것이 아마 좋은 생각일 것입니다. "최첨단"과 "회계"라는 용어는 함께 잘 어울리지 않습니다. 하지만 이 기준을 기술 결정에 가져오면, 잘못된 답을 얻기 시작합니다.

기술은 종종 _최첨단_이어야 합니다. 에란 갯(Erann Gat)이 지적했듯이, 프로그래밍 언어에서 "업계 모범 사례"가 실제로 가져다주는 것은 최고가 아니라 단지 평균일 뿐입니다. 어떤 결정이 당신이 더 공격적인 경쟁사보다 훨씬 느린 속도로 소프트웨어를 개발하게 만든다면, "모범 사례"는 잘못된 명칭입니다.

그래서 여기 제가 매우 가치 있다고 생각하는 두 가지 정보가 있습니다. 사실, 저는 제 경험을 통해 이것을 알고 있습니다. 첫째, 언어는 힘에 차이가 있습니다. 둘째, 대부분의 관리자들은 이를 의도적으로 무시합니다. 이 두 가지 사실은 말 그대로 돈을 버는 비결입니다. ITA는 이 비결이 실제로 작동하는 예시입니다. 소프트웨어 비즈니스에서 승리하고 싶다면, 찾을 수 있는 가장 어려운 문제를 맡고, 얻을 수 있는 가장 강력한 언어를 사용하며, 경쟁사들의 뾰족한 머리 상사들이 평균으로 회귀하기를 기다리세요.


부록: 힘

프로그래밍 언어의 상대적 힘에 대해 제가 의미하는 바를 설명하기 위해 다음 문제를 고려해 봅시다. 우리는 누산기(accumulator)를 생성하는 함수를 작성하고자 합니다--숫자 n을 받아 다른 숫자 i를 받아 n을 i만큼 증가시킨 값을 반환하는 함수를 반환하는 함수입니다.

(그것은 더하는 것이 아니라 증가시키는 것입니다. 누산기는 누적되어야 합니다.)

커먼 리스프에서는 (defun foo (n) (lambda (i) (incf n i)))가 되고, 펄 5에서는 sub foo { my ($n) = @_; sub {$n += shift} }가 됩니다. 펄에서는 매개변수를 수동으로 추출해야 하므로 리스프 버전보다 요소가 더 많습니다.

스몰토크(Smalltalk)에서는 코드가 리스프보다 약간 더 깁니다 foo: n |s| s := n. ^[:i| s := s+i. ]. 일반적으로 렉시컬 변수(lexical variables)가 작동하지만, 매개변수에 할당할 수 없으므로 새로운 변수 s를 생성해야 하기 때문입니다.

자바스크립트(Javascript)에서도 예시는 다시 약간 더 깁니다. 자바스크립트는 문장과 표현식 사이의 구별을 유지하므로, 값을 반환하기 위해 명시적인 return 문이 필요합니다: function foo(n) { return function (i) { return n += i } } (공정하게 말하자면, 펄도 이 구별을 유지하지만, return을 생략할 수 있게 함으로써 전형적인 펄 방식으로 처리합니다.)

리스프/펄/스몰토크/자바스크립트 코드를 파이썬으로 번역하려고 하면 몇 가지 제약에 부딪힙니다. 파이썬은 렉시컬 변수를 완전히 지원하지 않기 때문에, n의 값을 담을 데이터 구조를 생성해야 합니다. 그리고 파이썬은 함수 데이터 타입을 가지고 있지만, 리터럴 표현이 없으므로 (본문이 단일 표현식인 경우를 제외하고) 반환할 명명된 함수를 생성해야 합니다. 결과는 다음과 같습니다: def foo(n): s = [n] def bar(i): s[0] += i return s[0] return bar 파이썬 사용자들은 왜 def foo(n): return lambda i: return n += i 또는 심지어 def foo(n): lambda i: n += i와 같이 작성할 수 없는지 정당하게 물을 수 있으며, 제 생각에는 언젠가는 그렇게 될 것입니다. (하지만 파이썬이 리스프로 완전히 진화하기를 기다리고 싶지 않다면, 언제든지 그냥...)

객체 지향(OO) 언어에서는, 하나의 메서드와 둘러싸는 스코프의 각 변수를 대체할 필드를 가진 클래스를 정의함으로써 클로저(둘러싸는 스코프에 정의된 변수를 참조하는 함수)를 제한적으로 시뮬레이션할 수 있습니다. 이는 프로그래머가 렉시컬 스코프를 완전히 지원하는 언어에서 컴파일러가 수행할 종류의 코드 분석을 직접 하게 만들며, 두 개 이상의 함수가 동일한 변수를 참조하는 경우에는 작동하지 않지만, 이와 같은 간단한 경우에는 충분합니다.

파이썬 전문가들은 파이썬에서 이 문제를 해결하는 선호되는 방식이 def foo(n): class acc: def __init__(self, s): self.s = s def inc(self, i): self.s += i return self.s return acc(n).inc 또는 class foo: def __init__(self, n): self.n = n def __call__(self, i): self.n += i return self.n와 같이 작성하는 것이라고 동의하는 것 같습니다. 제가 이들을 포함시킨 이유는 파이썬 옹호자들이 제가 언어를 잘못 표현했다고 말하는 것을 원치 않기 때문이지만, 둘 다 첫 번째 버전보다 더 복잡해 보입니다. 당신은 동일한 작업을 수행하고 있습니다. 즉, 누산기를 저장할 별도의 공간을 설정하는 것입니다; 단지 리스트의 헤드 대신 객체의 필드일 뿐입니다. 그리고 이러한 특별하고 예약된 필드 이름, 특히 __call__의 사용은 약간의 해킹처럼 보입니다.

펄과 파이썬의 경쟁에서 파이썬 해커들의 주장은 파이썬이 펄에 대한 더 우아한 대안이라는 것 같지만, 이 사례가 보여주는 것은 힘이 궁극적인 우아함이라는 것입니다: 펄 프로그램은 구문이 약간 더 못생겼더라도 더 간단합니다 (요소가 더 적습니다).

다른 언어들은 어떨까요? 이 강연에서 언급된 다른 언어들--포트란, C, C++, 자바, 비주얼 베이직--에서는 이 문제를 실제로 해결할 수 있는지 불분명합니다. 켄 앤더슨(Ken Anderson)은 자바에서 얻을 수 있는 가장 가까운 코드가 다음과 같다고 말합니다:

public interface Inttoint {
  public int call(int i);
}

public static Inttoint foo(final int n) {
  return new Inttoint() {
    int s = n;
    public int call(int i) {
    s = s + i;
    return s;
    }};
}

이것은 정수에만 작동하기 때문에 사양에 미치지 못합니다. 자바 해커들과 많은 이메일 교환 끝에, 저는 이전 예시들처럼 작동하는 제대로 된 다형성 버전을 작성하는 것이 지독히 어색하거나 불가능에 가깝다고 말하고 싶습니다. 만약 누군가 작성하고 싶다면 매우 궁금하겠지만, 저는 개인적으로 시간 초과되었습니다.

물론 다른 언어로 이 문제를 해결할 수 없다는 것이 문자 그대로 사실은 아닙니다. 이 모든 언어가 튜링 동등하다는 사실은 엄밀히 말해 어떤 언어로든 어떤 프로그램이든 작성할 수 있다는 의미입니다. 그렇다면 어떻게 할까요? 극한의 경우, 덜 강력한 언어로 리스프 인터프리터를 작성하는 것입니다.

농담처럼 들리겠지만, 대규모 프로그래밍 프로젝트에서 다양한 정도로 너무 자주 발생하여 이 현상에는 이름이 붙어 있습니다. 그린스펀의 열 번째 법칙(Greenspun's Tenth Rule):

충분히 복잡한 C 또는 포트란 프로그램은 커먼 리스프의 절반에 해당하는 임시적이고 비공식적으로 명시된, 버그가 많고 느린 구현을 포함한다.

어려운 문제를 해결하려고 한다면, 문제는 충분히 강력한 언어를 사용할 것인지가 아니라, (a) 강력한 언어를 사용할 것인지, (b) 사실상의 인터프리터를 작성할 것인지, 아니면 (c) 스스로 그 언어의 인간 컴파일러가 될 것인지입니다. 우리는 이미 파이썬 예시에서 이것이 시작되는 것을 보고 있습니다. 거기서 우리는 렉시컬 변수를 구현하기 위해 컴파일러가 생성할 코드를 사실상 시뮬레이션하고 있습니다.

이러한 관행은 흔할 뿐만 아니라 제도화되어 있습니다. 예를 들어, 객체 지향(OO) 세계에서는 "패턴"에 대해 많이 듣습니다. 저는 이러한 패턴이 때로는 (c) 사례, 즉 인간 컴파일러가 작동하고 있다는 증거가 아닌지 궁금합니다. 제 프로그램에서 패턴을 볼 때, 저는 그것을 문제의 징후로 간주합니다. 프로그램의 형태는 해결해야 할 문제만을 반영해야 합니다. 코드의 다른 어떤 규칙성도, 적어도 저에게는, 제가 충분히 강력하지 않은 추상화를 사용하고 있다는 신호입니다--종종 제가 작성해야 할 어떤 매크로의 확장을 수동으로 생성하고 있다는 신호입니다.

각주

  • IBM 704 CPU는 냉장고 크기였지만 훨씬 무거웠습니다. CPU는 3150파운드였고, 4K RAM은 별도의 상자에 담겨 4000파운드 더 나갔습니다. 가장 큰 가정용 냉장고 중 하나인 Sub-Zero 690은 656파운드입니다.

  • 스티브 러셀은 또한 1962년에 최초의 (디지털) 컴퓨터 게임인 스페이스워(Spacewar)를 작성했습니다.

  • 뾰족한 머리의 상사를 속여 리스프로 소프트웨어를 작성하게 하고 싶다면, 그것이 XML이라고 말해보세요.

  • 다른 리스프 방언에서의 누산기 생성기는 다음과 같습니다: Scheme: (define (foo n) (lambda (i) (set! n (+ n i)) n)) Goo: (df foo (n) (op incf n _))) Arc: (def foo (n) [++ n _])

  • 에란 갯의 JPL에서의 "업계 모범 사례"에 대한 슬픈 이야기는 제가 이 일반적으로 잘못 적용되는 문구를 다루도록 영감을 주었습니다.

  • 피터 노빅(Peter Norvig)은 _디자인 패턴(Design Patterns)_에 있는 23가지 패턴 중 16가지가 리스프에서는 "보이지 않거나 더 간단하다"는 것을 발견했습니다.

  • 다양한 언어에 대한 질문에 답해주고/또는 이 초고를 읽어주신 켄 앤더슨, 트레버 블랙웰, 에란 갯, 댄 기핀, 사라 할린, 제레미 힐튼, 로버트 모리스, 피터 노빅, 가이 스틸, 안톤 반 스트라텐을 포함한 많은 분들께 감사드립니다. 이 글에 표현된 어떤 의견에 대해서도 그들에게 비난의 여지는 없습니다.

관련 글:

많은 분들이 이 강연에 대해 응답해 주셨으므로, 그들이 제기한 문제들을 다루기 위해 추가 페이지를 마련했습니다: Re: 너드들의 복수.

또한 LL1 메일링 리스트에서 광범위하고 종종 유용한 토론을 촉발했습니다. 특히 안톤 반 스트라텐의 의미론적 압축에 대한 메일을 참조하세요.

LL1의 일부 메일은 제가 간결함은 힘이다에서 언어의 힘이라는 주제를 더 깊이 파고들도록 이끌었습니다.

누산기 생성기 벤치마크의 더 많은 표준 구현들이 자체 페이지에 모여 있습니다.

일본어 번역, 스페인어 번역, 중국어 번역