(Эта статья была написана как своего рода бизнес-план для нового языка. Поэтому в ней отсутствует (так как принимается как нечто само собой разумеющееся) самое важное свойство хорошего языка программирования: очень мощные абстракции.)
Один мой друг однажды сказал выдающемуся эксперту по операционным системам, что хочет разработать по-настоящему хороший язык программирования. Эксперт ответил ему, что это пустая трата времени, что языки программирования становятся популярными или непопулярными вовсе не благодаря своим достоинствам, и поэтому, насколько бы хорош ни был его язык, никто не станет им пользоваться. По крайней мере, именно это произошло с языком, который разработал он сам.
Что же делает язык популярным? Заслуживают ли популярные языки своей популярности? Стоит ли пытаться создать хороший язык программирования? И как бы вы это сделали?
Я думаю, что ответы на эти вопросы можно найти, присмотревшись к хакерам и поняв, чего они хотят. Языки программирования создаются для хакеров, и язык программирования хорош именно как язык программирования (а не, скажем, как упражнение в денотационной семантике или разработке компиляторов) тогда и только тогда, когда он нравится хакерам.
1 Механика популярности
Разумеется, это правда, что большинство людей выбирают языки программирования не просто исходя из их достоинств. Большинству программистов о том, какой язык использовать, сообщает кто-то другой. И всё же я считаю, что влияние подобных внешних факторов на популярность языков программирования не так велико, как иногда принято думать. Более серьезная проблема, на мой взгляд, заключается в том, что представление хакера о хорошем языке программирования не совпадает с представлениями большинства создателей языков.
Из этих двух мнений имеет значение именно мнение хакера. Языки программирования — это не теоремы. Это инструменты, созданные для людей, и они должны подходить под человеческие достоинства и недостатки точно так же, как обувь должна подходить под человеческие ноги. Если туфля жмет, когда ее надеваешь, это плохая туфля, какой бы изящной скульптурой она ни казалась.
Возможно, большинство программистов не в состоянии отличить хороший язык от плохого. Но с любым другим инструментом дело обстоит точно так же. Это не означает, что пытаться разработать хороший язык — пустая трата времени. Хакеры-эксперты сразу видят хороший язык и будут им пользоваться. Хакеры-эксперты, надо признать, составляют крошечное меньшинство, но именно это крошечное меньшинство пишет весь хороший софт, и их влияние таково, что остальные программисты будут тяготеть к тому языку, который используют они. Зачастую это даже не просто влияние, а прямое распоряжение: хакеры-эксперты нередко оказываются теми самыми людьми, которые в качестве начальников или научных руководителей говорят другим программистам, какой язык использовать.
Мнение хакеров-экспертов — не единственная сила, определяющая относительную популярность языков программирования: унаследованный софт (Cobol) и хайп (Ada, Java) тоже играют свою роль, — но в долгосрочной перспективе, как мне кажется, это самая мощная сила. При наличии начальной критической массы и достаточного времени язык программирования, вероятно, становится примерно настолько популярным, насколько он того заслуживает. И популярность лишь еще сильнее отделяет хорошие языки от плохих, поскольку обратная связь от реальных живых пользователей всегда ведет к улучшениям. Посмотрите, насколько сильно менялся любой популярный язык на протяжении своего жизненного цикла. Perl и Fortran — крайние случаи, но даже Lisp сильно изменился. В Lisp 1.5, например, не было макросов; они развились позже, после того как хакеры в MIT провели пару лет, используя Lisp для написания реальных программ. [1]
Поэтому, должен ли язык быть хорошим, чтобы стать популярным, или нет, но я считаю, что язык должен быть популярным, чтобы быть хорошим. И он должен оставаться популярным, чтобы оставаться хорошим. Современный уровень в языках программирования не стоит на месте. И всё же те Lisp, которые есть у нас сегодня, — это во многом всё то же самое, что было в MIT в середине 1980-х, потому что это был последний раз, когда у Lisp была достаточно большая и требовательная база пользователей.
Конечно, хакеры должны узнать о языке, прежде чем смогут им воспользоваться. Откуда они услышат о нем? От других хакеров. Но должна быть какая-то начальная группа хакеров, использующих этот язык, чтобы другие вообще смогли о нем услышать. Интересно, насколько большой должна быть эта группа; сколько пользователей составляют критическую массу? Навскидку я бы сказал — двадцать. Если бы у языка было двадцать отдельных пользователей, то есть двадцать пользователей, которые сами решили его использовать, я бы счел его жизнеспособным.
Добиться этого вряд ли легко. Я не удивлюсь, если пройти путь от нуля до двадцати пользователей окажется труднее, чем от двадцати до тысячи. Лучший способ заполучить эти первые двадцать пользователей — вероятно, применить троянского коня: дать людям нужное им приложение, которое случайно оказалось написанным на новом языке.
2 Внешние факторы
Начнем с признания одного внешнего фактора, который действительно влияет на популярность языка программирования. Чтобы стать популярным, язык программирования должен быть языком сценариев для популярной системы. Fortran и Cobol были языками сценариев для ранних мейнфреймов IBM. C был языком сценариев для Unix, а позже им стал и Perl. Tcl — это язык сценариев для Tk. Java и Javascript предназначались для роли языков сценариев веб-браузеров.
Lisp не стал массово популярным языком, потому что он не является языком сценариев для массово популярной системы. Та популярность, которую он сохраняет, восходит к 1960-м и 1970-м годам, когда он был языком сценариев в MIT. Многие выдающиеся программисты того времени так или иначе были связаны с MIT. А в начале 1970-х, до появления C, созданный в MIT диалект Lisp, называвшийся MacLisp, был одним из немногих языков программирования, которыми серьезный хакер захотел бы пользоваться.
Сегодня Lisp является языком сценариев двух умеренно популярных систем — Emacs и Autocad, и по этой причине я подозреваю, что большая часть программ на Lisp сегодня пишется на Emacs Lisp или AutoLisp.
Языки программирования не существуют в изоляции. Английский глагол «to hack» переходный — хакеры обычно хакают что-то конкретное, — и на практике языки оценивают применительно к тому, для чего их используют. Так что если вы хотите создать популярный язык, вам придется либо предоставить нечто большее, чем просто язык, либо разработать свой язык так, чтобы он заменил язык сценариев какой-то существующей системы.
Common Lisp непопулярен отчасти потому, что он сирота. Изначально у него была система: Lisp-машина. Но Lisp-машины (наряду с параллельными компьютерами) были раздавлены растущей мощью процессоров общего назначения в 1980-х годах. Common Lisp мог бы остаться популярным, если бы оказался хорошим языком сценариев для Unix. Но он, увы, ужасающе для этого непригоден.
Эту ситуацию можно описать словами, что язык не судят исключительно по его достоинствам. Другой взгляд заключается в том, что язык программирования на самом деле не является языком программирования, если он одновременно не выступает для чего-то языком сценариев. Это кажется несправедливым только в том случае, если становится неожиданностью. По-моему, это ничуть не более несправедливо, чем ожидать от языка программирования наличия, скажем, реализации. Это просто часть того, чем язык программирования и является.
Языку программирования, конечно, нужна хорошая реализация, и она должна быть бесплатной. Компании будут платить за софт, но отдельные хакеры — нет, а привлечь вам нужно именно хакеров.
О языке также должна быть написана книга. Книга должна быть тонкой, хорошо написанной и полной хороших примеров. K&R здесь — идеал. В данный момент я бы почти сказал, что о языке должна быть книга, изданная O'Reilly. Это становится критерием того, что язык действительно важен для хакеров.
Должна быть и онлайн-документация. На самом деле книга вполне может начинаться как онлайн-документация. Но я не думаю, что бумажные книги уже вышли из моды. Их формат удобен, а фактическая цензура, накладываемая издательствами, служит полезным, хоть и несовершенным фильтром. Книжные магазины — одно из важнейших мест, где люди узнают о новых языках.
3 Краткость
Допустим, вы можете предоставить три вещи, необходимые любому языку: бесплатную реализацию, книгу и то, ради чего программировать, — как же сделать язык, который понравится хакерам?
Одна из вещей, которые нравятся хакерам, — это краткость. Хакеры ленивы точно так же, как ленивы математики и архитекторы-модернисты: они ненавидят всё лишнее. Не будет сильным преувеличением сказать, что хакер, собираясь писать программу, по крайней мере подсознательно выбирает язык исходя из общего количества символов, которые ему придется напечатать. Даже если хакеры думают не совсем так, создателю языка имело бы смысл действовать так, будто это именно так.
Пытаться нянчиться с пользователем, используя многословные конструкции, которые должны напоминать английский язык, — это ошибка. Cobol печально известен этим недостатком. Просьбу написать
add x to y giving z
вместо
z = x+y
хакер воспримет как нечто среднее между оскорблением его интеллекта и грехом перед Богом.
Иногда говорили, что в Lisp следовало бы использовать first и rest вместо car и cdr, потому что это сделало бы программы легче для чтения. Может быть, в течение первых двух часов. Но хакер достаточно быстро усвоит, что car означает первый элемент списка, а cdr — остаток. Использование first и rest означает, что печатать придется на 50% больше. К тому же они разной длины, а это значит, что аргументы не будут выравниваться по вертикали при вызовах на соседних строках, как это часто бывает с car и cdr. Я обнаружил, что то, как код выравнивается на странице, имеет огромное значение. Я с трудом могу читать код на Lisp, если он набран пропорциональным шрифтом, и друзья говорят, что это справедливо и для других языков.
Краткость — это область, где языки со статической типизацией проигрывают. При прочих равных никто не хочет начинать программу с кучи объявлений. Всё, что может быть неявным, должно быть неявным.
Отдельные токены тоже должны быть короткими. Perl и Common Lisp занимают противоположные полюса в этом вопросе. Программы на Perl могут быть почти криптически плотными, в то время как имена встроенных операторов Common Lisp комично длинны. Создатели Common Lisp, вероятно, ожидали, что у пользователей будут текстовые редакторы, которые будут вводить эти длинные имена за них. Но издержки длинного имени — это не только издержки на его ввод. Есть еще издержки на его чтение и издержки на место, которое оно занимает на экране.
4 Хакерская податливость
Для хакера есть вещь поважнее краткости: возможность делать то, что хочется. В истории языков программирования удивительно много сил ушло на то, чтобы запретить программистам делать вещи, считающиеся «неподобающими». Это опасно самонадеянный замысел. Откуда создателю языка знать, что программисту потребуется сделать? Думаю, создателям языков лучше считать своего целевого пользователя гением, которому потребуется делать вещи, о которых они и не помышляли, а не недотепой, которого нужно защищать от самого себя. Недотепа в любом случае выстрелит себе в ногу. Вы можете уберечь его от обращения к переменным в другом пакете, но вы не убережете его от написания плохо спроектированной программы для решения не той задачи, на которую уйдет целая вечность.
Хорошие программисты часто хотят делать опасные и сомнительные вещи. Под сомнительными я имею в виду вещи, которые лезут за кулисы любого семантического фасада, который пытается выстроить язык: например, добраться до внутреннего представления какой-нибудь высокоуровневой абстракции. Хакеры любят хакать, а хакать означает залезать внутрь вещей и спорить с замыслом первоначального создателя.
Позвольте спорить со своим замыслом. Когда вы создаете любой инструмент, люди используют его способами, которых вы не предполагали, и это особенно верно для такого сложного и тонкого инструмента, как язык программирования. Многие хакеры захотят подправить вашу семантическую модель так, как вы никогда и представить себе не могли. Я говорю: позвольте им; дайте программисту доступ к максимальному количеству внутренних механизмов, насколько это возможно без риска для рантайм-систем вроде сборщика мусора.
В Common Lisp мне часто хотелось пройтись по полям структуры — например, чтобы вычистить ссылки на удаленный объект или найти неинициализированные поля. Я знаю, что под капотом структуры — это всего лишь векторы. И всё же я не могу написать функцию общего назначения, которую можно было бы вызвать для любой структуры. Я могу обращаться к полям только по имени, потому что именно в этом якобы и заключается смысл структуры.
Возможно, хакеру захочется обойти задуманную модель всего один или два раза за всю большую программу. Но какая же колоссальная разница, когда у него есть такая возможность! И дело здесь может быть не только в решении задачи. В этом есть и своего рода удовольствие. Хакеры разделяют тайное удовольствие хирурга, копающегося в неприглядных внутренностях, или тайное удовольствие подростка, выдавливающего прыщи. [2] По крайней мере, для парней определенного рода ужасы притягательны. Журнал Maxim публикует ежегодный фотоальбом, содержащий смесь фотографий полуобнаженных красоток и жутких аварий. Они знают свою аудиторию.
Исторически сложилось так, что Lisp отлично умел идти навстречу хакерам. Политкорректность Common Lisp — это аномалия. Ранние Lisp позволяли дотянуться абсолютно до всего. Значительная часть этого духа, к счастью, сохранилась в макросах. Какая замечательная вещь — возможность производить произвольные преобразования над исходным кодом!
Классические макросы — это инструмент настоящего хакера: простой, мощный и опасный. Понять, что они делают, очень легко: вы вызываете функцию с аргументами макроса, и всё, что она возвращает, подставляется вместо вызова макроса. Гигиенические макросы воплощают противоположный принцип. Они пытаются уберечь вас от понимания того, что они делают. Я ни разу не слышал, чтобы гигиенические макросы объяснили в одном предложении. И они являются классическим примером опасности решать за программистов, что им позволено хотеть. Гигиенические макросы призваны, помимо прочего, защитить меня от захвата переменных, но захват переменных — это именно то, чего я хочу в некоторых макросах.
По-настоящему хороший язык должен быть одновременно и чистым, и грязным: чисто спроектированным, с небольшим ядром хорошо понятных и в высшей степени ортогональных операторов, но грязным в том смысле, что он позволяет хакерам делать с собой всё, что им вздумается. Таков C. Такими были и ранние версии Lisp. Настоящий хакерский язык всегда будет иметь слегка развязный характер.
В хорошем языке программирования должны быть возможности, от которых люди, использующие фразу «программная инженерия», будут неодобрительно качать головой. На другом конце спектра находятся такие языки, как Ada и Pascal — образцы благопристойности, которые хороши для преподавания и мало для чего еще.
5 Одноразовые программы
Чтобы быть привлекательным для хакеров, язык должен хорошо подходить для написания тех программ, которые они хотят писать. А это означает — возможно, неожиданно, — что он должен хорошо подходить для написания одноразовых программ.
Одноразовая программа — это программа, которую вы быстро пишете для какой-то ограниченной задачи: программа для автоматизации системного администрирования, или генерации тестовых данных для симуляции, или преобразования данных из одного формата в другой. Удивительная особенность одноразовых программ состоит в том, что, подобно «временным» зданиям, построенным во многих американских университетах во время Второй мировой войны, их часто так и не выбрасывают. Многие из них перерастают в настоящие программы с реальными возможностями и настоящими пользователями.
У меня есть подозрение, что лучшие большие программы начинают свою жизнь именно так, а не проектируются большими с самого начала, подобно плотине Гувера. Создавать что-то масштабное с нуля — страшно. Когда люди берутся за проект, который слишком велик, они оказываются перегружены. Проект либо вязнет в болоте, либо результат получается стерильным и топорным: торговый центр вместо исторического центра города, Бразилиа вместо Рима, Ada вместо C.
Другой способ получить большую программу — начать с одноразовой программы и продолжать улучшать ее. Такой подход менее пугающий, и архитектура программы только выигрывает от эволюции. Я думаю, если приглядеться, выяснится, что большинство больших программ развивалось именно так. А те, что развивались таким путем, вероятно, до сих пор написаны на том языке, на котором они были написаны изначально, потому что программы редко портируют, если только не по политическим причинам. И поэтому, как ни парадоксально, если вы хотите сделать язык, который используется для больших систем, вы должны сделать его пригодным для написания одноразовых программ, потому что именно из них большие системы и рождаются.
Perl — яркий тому пример. Он был не просто создан для написания одноразовых программ, но и сам во многом был одноразовой программой. Perl начинался как набор утилит для генерации отчетов и превратился в язык программирования лишь по мере того, как одноразовые программы, которые люди писали на нем, становились всё крупнее. Лишь к выходу Perl 5 (если вообще тогда) язык стал пригоден для написания серьезных программ, и тем не менее он уже был невероятно популярен.
Что делает язык подходящим для одноразовых программ? Для начала, он должен быть легкодоступным. Одноразовая программа — это то, что вы рассчитываете написать за час. Поэтому язык, скорее всего, уже должен быть установлен на том компьютере, которым вы пользуетесь. Это не может быть что-то, что вам нужно сначала установить, прежде чем воспользоваться. Он должен быть прямо под рукой. C был под рукой, потому что поставлялся вместе с операционной системой. Perl был под рукой, потому что изначально был инструментом для системных администраторов, и ваш сисадмин уже установил его.
Однако быть доступным означает нечто большее, чем просто быть установленным. Интерактивный язык с интерфейсом командной строки доступнее языка, который нужно отдельно компилировать и запускать. Популярный язык программирования должен быть интерактивным и быстро запускаться.
Еще одна вещь, которая требуется от одноразовой программы, — это краткость. Краткость всегда привлекает хакеров, и ни в чем она не привлекает их сильнее, чем в программе, которую они планируют набросать за час.
6 Библиотеки
Конечно, верх краткости — это когда программа уже написана за вас, и остается лишь вызвать ее. И здесь мы подходим к тому, что, как мне кажется, станет всё более важной особенностью языков программирования: к библиотечным функциям. Perl выигрывает благодаря наличию обширных библиотек для работы со строками. Этот класс библиотечных функций особенно важен для одноразовых программ, которые часто изначально пишутся для преобразования или извлечения данных. Многие программы на Perl, вероятно, начинаются просто как пара библиотечных вызовов, склеенных вместе.
Я думаю, многие достижения в области языков программирования в следующие пятьдесят лет будут связаны именно с библиотечными функциями. Я полагаю, что в будущих языках библиотеки будут проектироваться столь же тщательно, сколь и само ядро языка. Разработка языков программирования будет заключаться не в том, делать ли язык статически или динамически типизированным, объектно-ориентированным, функциональным или еще каким-нибудь, а в том, как проектировать отличные библиотеки. Создатели языков, любящие размышлять о системах типов, могут содрогнуться от этой мысли. Это же почти то же самое, что писать прикладные приложения! Очень жаль. Языки создаются для программистов, а программистам нужны библиотеки.
Проектировать хорошие библиотеки трудно. Дело не просто в том, чтобы написать много кода. Когда библиотеки становятся слишком большими, порой на поиск нужной функции уходит больше времени, чем на то, чтобы написать этот код самому. Библиотеки должны проектироваться с использованием небольшого набора ортогональных операторов, точно так же, как и ядро языка. У программиста должна быть возможность догадаться, какой вызов библиотеки сделает то, что ему нужно.
Библиотеки — это одна из тех областей, где Common Lisp терпит неудачу. В нем есть лишь зачаточные библиотеки для работы со строками и почти ничего для взаимодействия с операционной системой. В силу исторических причин Common Lisp пытается делать вид, что ОС не существует. А поскольку вы не можете общаться с ОС, вы вряд ли сможете написать серьезную программу, используя только встроенные операторы Common Lisp. Вам придется использовать и хаки, специфичные для конкретной реализации, а они на практике обычно не дают вам всего, чего хочется. Хакеры ценили бы Lisp гораздо выше, если бы у Common Lisp были мощные библиотеки для работы со строками и хорошая поддержка ОС.
7 Синтаксис
Сможет ли язык с синтаксисом Lisp — или, точнее, с отсутствием синтаксиса — когда-нибудь стать популярным? Я не знаю ответа на этот вопрос. Но я думаю, что синтаксис — не главная причина, почему Lisp сейчас не популярен. У Common Lisp есть проблемы и похуже непривычного синтаксиса. Я знаю нескольких программистов, которые спокойно относятся к префиксной нотации и тем не менее по умолчанию используют Perl, потому что в нем есть мощные библиотеки для работы со строками и он умеет взаимодействовать с ОС.
С префиксной нотацией связаны две возможные проблемы: то, что она непривычна программистам, и то, что она недостаточно компактна. Общепринятое мнение в мире Lisp состоит в том, что настоящая проблема — именно первая. Я в этом не уверен. Да, префиксная нотация повергает обычных программистов в панику. Но я не думаю, что мнение обычных программистов имеет значение. Языки становятся популярными или непопулярными в зависимости от того, что о них думают хакеры-эксперты, а хакеры-эксперты, как мне кажется, способны справиться с префиксной нотацией. Синтаксис Perl может быть весьма невразумительным, но это не помешало его популярности. Если уж на то пошло, это, возможно, даже помогло зарождению культа Perl.
Куда более серьезная проблема — это многословность префиксной нотации. Для экспертов это действительно проблема. Никто не хочет писать (aref a x y), когда можно написать a[x,y].
В данном конкретном случае есть способ изящно обойти проблему. Если рассматривать структуры данных как функции от индексов, мы могли бы писать вместо этого (a x y), что даже короче, чем форма в Perl. Подобные приемы могут сократить и другие типы выражений.
Мы можем избавиться от множества скобок (или сделать их необязательными), сделав отступы значимыми. В любом случае именно так программисты и читают код: когда отступы говорят одно, а скобки-разделители — другое, мы ориентируемся на отступы. Придание отступам смыслового значения устранило бы этот частый источник ошибок, а заодно сделало бы программы короче.
Иногда инфиксный синтаксис проще для чтения. Это особенно верно для математических выражений. Я программирую на Lisp всю жизнь, но до сих пор не нахожу префиксные математические выражения естественными. И всё же иметь операторы, принимающие любое количество аргументов, очень удобно, особенно при генерации кода. Так что если мы и добавим инфиксный синтаксис, его, вероятно, следует реализовать в виде своего рода read-макроса.
Я не считаю, что мы должны религиозно противиться введению синтаксиса в Lisp, пока он понятным образом транслируется в лежащие в его основе s-выражения. В Lisp уже и так довольно много синтаксиса. Ввести еще немного — не обязательно плохо, при условии, что никого не заставляют им пользоваться. В Common Lisp некоторые разделители зарезервированы для языка, что указывает на то, что по крайней мере часть разработчиков планировала появление дополнительного синтаксиса в будущем.
Один из самых вопиюще не-лисповских синтаксических элементов в Common Lisp встречается в строках форматирования; format — это сам по себе отдельный язык, и этот язык — не Lisp. Если бы существовал план по внедрению в Lisp дополнительного синтаксиса, спецификаторы format можно было бы включить в него. Было бы здорово, если бы макросы могли генерировать спецификаторы формата точно так же, как они генерируют любой другой код.
Один выдающийся хакер Lisp рассказывал мне, что его экземпляр книги CLTL сам раскрывается на разделе format. Мой тоже. Вероятно, это указывает на потенциал для улучшений. А может быть, это также означает, что в программах приходится делать много операций ввода-вывода.
8 Эффективность
Хороший язык, как всем известно, должен генерировать быстрый код. Но на практике я не думаю, что быстрый код получается главным образом благодаря тому, что вы закладываете в дизайн языка. Как давным-давно отметил Кнут, скорость имеет значение лишь в определенных критических узких местах. И как с тех пор заметили многие программисты, люди очень часто ошибаются насчет того, где именно эти узкие места находятся.
Поэтому на практике путь к быстрому коду лежит через очень хороший профилировщик, а не через то, чтобы, скажем, сделать язык со строгой типизацией. Вам не нужно знать тип каждого аргумента в каждом вызове программы. Вам действительно нужно иметь возможность объявлять типы аргументов в узких местах. И более того, вам нужно уметь находить, где именно эти узкие места находятся.
Одна из претензий к Lisp заключалась в том, что в нем трудно понять, какие операции ресурсоемки. Возможно, это правда. Возможно, это даже неизбежно, если вам нужен очень абстрактный язык. И в любом случае, я думаю, хороший профилировщик во многом помог бы решить эту проблему: вы бы быстро поняли, что обходится дорого.
Часть проблемы здесь носит социальный характер. Разработчики языков любят писать быстрые компиляторы. Именно этим они измеряют свое мастерство. О профилировщике они думают в лучшем случае как о второстепенном дополнении. Но на практике хороший профилировщик может сделать для повышения скорости реальных программ, написанных на этом языке, больше, чем компилятор, генерирующий быстрый код. И здесь разработчики языков снова несколько оторваны от своих пользователей. Они проделывают великолепную работу, решая немного не ту задачу.
Возможно, было бы хорошей идеей иметь активный профилировщик — передавать данные о производительности программисту, а не ждать, пока он сам придет за ними. Например, редактор мог бы выделять узкие места красным цветом, когда программист редактирует исходный код. Другой подход — каким-то образом отображать происходящее в работающих программах. Это дало бы особенно большой выигрыш в серверных приложениях, где одновременно работает множество программ, за которыми нужно следить. Активный профилировщик мог бы графически показывать, что происходит в памяти во время работы программы, или даже издавать звуки, сообщающие о происходящем.
Звук — отличная подсказка о неполадках. В одном месте, где я работал, у нас была большая панель со стрелочными индикаторами, показывавшими, что происходит с нашими веб-серверами. Стрелки двигались маленькими сервоприводами, которые слегка шумели при повороте. Я не видел панели со своего рабочего места, но обнаружил, что по звуку могу мгновенно определить, когда с сервером возникла проблема.
Возможно, удалось бы даже написать профилировщик, который автоматически обнаруживал бы неэффективные алгоритмы. Я не удивлюсь, если определенные паттерны доступа к памяти окажутся верными признаками плохих алгоритмов. Если бы внутри компьютера бегал маленький человечек, выполняющий наши программы, он, вероятно, мог бы поведать о своей работе такую же длинную и жалобную повесть, как федеральный служащий. У меня часто возникает ощущение, что я заставляю процессор гоняться за призраками, но у меня никогда не было хорошего способа посмотреть, чем именно он занят.
Некоторые реализации Lisp сейчас компилируются в байт-код, который затем выполняется интерпретатором. Обычно это делается для того, чтобы реализацию было проще портировать, но это могло бы стать полезной особенностью языка. Возможно, стоило бы сделать байт-код официальной частью языка и разрешить программистам использовать инлайновый байт-код в узких местах. Тогда подобные оптимизации тоже были бы переносимыми.
Сама природа скорости в восприятии конечного пользователя, возможно, меняется. С расцветом серверных приложений все больше и больше программ могут упираться во ввод-вывод (i/o-bound). Сделать ввод-вывод быстрым станет важной задачей. Язык может помочь как прямолинейными мерами вроде простых и быстрых функций форматированного вывода, так и глубокими структурными изменениями, такими как кэширование и персистентные объекты.
Пользователей интересует время отклика. Но все более важной будет становиться другая разновидность эффективности: число одновременных пользователей, которых вы можете поддерживать на один процессор. Многие из интересных приложений, которые появятся в ближайшем будущем, будут серверными, и количество пользователей на сервер — критически важный вопрос для любого, кто размещает такие приложения. В капитальных затратах бизнеса, предоставляющего серверное приложение, это делитель.
Годами эффективность мало волновала большинство разработчиков пользовательских приложений. Они могли рассчитывать на то, что на столе у каждого пользователя будет стоять все более мощный процессор. И по закону Паркинсона программное обеспечение разрасталось, заполняя доступные ресурсы. С серверными приложениями все изменится. В этом мире аппаратное и программное обеспечение будут поставляться вместе. Для компаний, предлагающих серверные приложения, число пользователей, которых они могут обслуживать на одном сервере, будет иметь огромное значение для итоговой прибыли.
В некоторых приложениях ограничивающим фактором будет процессор, и скорость выполнения станет главной целью оптимизации. Но часто узким местом будет память; число одновременных пользователей будет определяться объемом памяти, необходимым для данных каждого пользователя. Язык может помочь и здесь. Хорошая поддержка потоков позволит всем пользователям делить общую кучу (heap). Также могут помочь персистентные объекты и/или поддержка ленивой загрузки на уровне языка.
9 Время
Последний ингредиент, необходимый популярному языку, — это время. Никто не хочет писать программы на языке, который может исчезнуть, как это случается со столь многими языками программирования. Поэтому большинство хакеров предпочтут подождать пару лет после появления языка, прежде чем вообще задуматься о его использовании.
Создатели замечательных новинок часто с удивлением обнаруживают это, но чтобы донести любую мысль до людей, требуется время. Один мой друг редко делает что-то с первого раза, когда его о чем-то просят. Он знает, что люди иногда просят о вещах, которые, как потом оказывается, им вовсе не нужны. Чтобы не тратить время попусту, он ждет третьего или четвертого раза, когда его попросят; к тому моменту просящий может быть изрядно раздражен, но, по крайней мере, ему действительно нужно то, о чем он просит.
Большинство людей научились схожим образом фильтровать все новое, о чем они слышат. Они даже не обращают внимания, пока не услышат о чем-то раз десять. И они совершенно правы: большинство громких новинок на поверку оказываются пустой тратой времени и в конце концов сходят на нет. Отложив изучение VRML, я вообще избавил себя от необходимости его учить.
Поэтому любому, кто изобретает что-то новое, приходится быть готовым повторять свою мысль годами, прежде чем люди начнут ее улавливать. Мы написали то, что, насколько мне известно, было первым серверным веб-приложением, и нам потребовались годы, чтобы донести до людей: его не нужно скачивать. Дело не в том, что они были глупы. Они просто от нас отгородились.
Хорошая новость в том, что простое повторение решает проблему. Все, что вам нужно делать, — это продолжать рассказывать свою историю, и со временем люди начнут слышать. Внимание привлекает не то, что вы появились; внимание привлекает то, что вы все еще здесь.
И даже хорошо, что на разгон обычно требуется время. Большинство технологий значительно развиваются уже после первого запуска — особенно языки программирования. Ничего лучше нельзя пожелать новой технологии, чем несколько лет использования лишь небольшим числом ранних последователей. Ранние последователи искушены и требовательны, они быстро выявят все оставшиеся в вашей технологии недостатки. Когда у вас мало пользователей, вы можете быть в тесном контакте со всеми ними. И ранние последователи снисходительны, когда вы улучшаете систему, даже если это что-то ломает.
Есть два способа внедрения новых технологий: метод органического роста и метод «большого взрыва». Органический рост иллюстрирует классический полукустарный гаражный стартап без денег. Пара парней в безвестности разрабатывает какую-то новую технологию. Они запускают ее без всякого маркетинга, и поначалу у них всего несколько (фанатично преданных) пользователей. Они продолжают улучшать технологию, а пользовательская база тем временем растет благодаря сарафанному радио. Не успели оглянуться — и они уже гиганты.
Другой подход, метод большого взрыва, иллюстрирует стартап с венчурным финансированием и масштабным маркетингом. Они спешат разработать продукт, запускают его с большой помпой и немедленно (как они надеются) получают огромную пользовательскую базу.
Обычно ребята из гаража завидуют парням большого взрыва. Те лощеные, уверенные в себе и пользуются уважением венчурных инвесторов. Они могут позволить себе все самое лучшее, а PR-кампания запуска попутно превращает их в знаменитостей. Сторонники органического роста, сидя в своем гараже, чувствуют себя бедными и обделенными вниманием. И все же, я думаю, они напрасно жалеют себя. Органический рост обычно дает более качественные технологии и более богатых основателей, чем метод большого взрыва. Если взглянуть на доминирующие сегодня технологии, выяснится, что большинство из них выросло органически.
Эта закономерность касается не только компаний. Ее можно увидеть и в финансируемых исследованиях. Multics и Common Lisp были проектами большого взрыва, а Unix и MacLisp — проектами органического роста.
10 Редизайн
«Лучшее писательство — это переписывание», — отмечал Э. Б. Уайт. Любой хороший писатель знает это, и для программного обеспечения это столь же справедливо. Важнейшая часть проектирования — это перепроектирование. Языки программирования, в особенности, переделывают недостаточно часто.
Чтобы писать хорошее ПО, нужно одновременно удерживать в голове две противоположные мысли. Вам нужна наивная вера молодого хакера в свои силы и в то же время скептицизм ветерана. Вы должны уметь думать одной половиной мозга: да что тут сложного?, думая в этот же момент другой: это никогда не сработает.
Фокус в том, чтобы понять: здесь нет настоящего противоречия. Вам нужно быть оптимистом и скептиком в отношении двух разных вещей. Вы должны сохранять оптимизм относительно самой возможности решить задачу, но быть скептически настроены к ценности любого решения, полученного вами на данный момент.
Люди, делающие хорошую работу, часто считают, что то, над чем они трудятся, никуда не годится. Другие смотрят на сделанное ими и восхищаются, но создатель полон тревоги. Эта закономерность не случайна: именно тревога и сделала работу качественной.
Если вам удастся удерживать надежду и тревогу в равновесии, они будут двигать проект вперед так же, как две ноги толкают велосипед. На первой фазе этого двухтактного двигателя инноваций вы яростно работаете над задачей, вдохновленные уверенностью в том, что сумеете ее решить. На второй фазе вы смотрите на то, что сделали, в холодном свете утра и предельно ясно видите все недостатки. Но пока ваш критический настрой не перевешивает надежду, вы сможете взглянуть на свою, пусть и неполную, систему и подумать: «да что тут сложного — доделать оставшееся?», продолжая тем самым цикл.
Удерживать эти две силы в равновесии непросто. У молодых хакеров преобладает оптимизм. Они что-то создают, убеждены, что это прекрасно, и больше никогда ничего не улучшают. У старых хакеров преобладает скептицизм, и они даже не решаются браться за амбициозные проекты.
Все, что помогает поддерживать цикл редизайна, идет на пользу. Прозу можно переписывать снова и снова, пока вы не останетесь довольны. Но ПО, как правило, перепроектируют недостаточно. У прозы есть читатели, но у программ есть пользователи. Если писатель перепишет эссе, люди, прочитавшие старую версию, вряд ли будут жаловаться, что их мысли сломались из-за какой-то возникшей несовместимости.
Пользователи — обоюдоострый меч. Они могут помочь вам улучшить язык, но могут и помешать его совершенствованию. Поэтому выбирайте пользователей осмотрительно и не спешите наращивать их число. Заводить пользователей — все равно что заниматься оптимизацией: мудрый подход заключается в том, чтобы отложить это на потом. Кроме того, как правило, в любой момент времени можно позволить себе изменить гораздо больше, чем кажется. Внесение изменений подобно срыванию пластыря: боль становится воспоминанием почти сразу, как только ее почувствуешь.
Всем известно, что разрабатывать язык комитетом — плохая идея. Комитеты порождают плохой дизайн. Но я думаю, что худшая опасность комитетов в том, что они мешают редизайну. Внесение изменений требует таких колоссальных усилий, что никто не хочет с этим связываться. Все, что решает комитет, обычно так и остается, даже если большинству участников это не по душе.
Даже комитет из двух человек мешает редизайну. Особенно часто это происходит на стыке фрагментов ПО, написанных двумя разными людьми. Чтобы изменить интерфейс, оба должны одновременно согласиться на это. И поэтому интерфейсы обычно не меняются вовсе, что становится проблемой, ведь они, как правило, оказываются одной из самых ситуативных и наспех сделанных частей любой системы.
Одним из решений могло бы стать проектирование систем таким образом, чтобы интерфейсы были горизонтальными, а не вертикальными — чтобы модули всегда представляли собой вертикально выстроенные слои абстракции. Тогда интерфейс, скорее всего, будет принадлежать одному из них. Нижний из двух уровней будет либо языком, на котором написан верхний (в таком случае нижний уровень владеет интерфейсом), либо подчиненным модулем (в таком случае интерфейс может диктоваться верхним уровнем).
11 Lisp
Из всего этого следует, что надежда на новый Lisp есть. Надежда есть на любой язык, который дает хакерам то, чего они хотят, включая Lisp. Думаю, мы, возможно, ошибались, полагая, будто хакеров отпугивает необычность Lisp. Эта утешительная иллюзия, возможно, мешала нам увидеть настоящую проблему с Lisp — или, по крайней мере, с Common Lisp, — которая заключается в том, что он никуда не годится для задач, которыми хотят заниматься хакеры. Языку хакеров нужны мощные библиотеки и то, над чем можно поработать. В Common Lisp нет ни того, ни другого. Язык хакеров лаконичен и гибок для доработок. Common Lisp — нет.
Хорошая новость в том, что плох не Lisp, а Common Lisp. Если мы сможем создать новый Lisp, который станет настоящим языком для хакеров, я думаю, хакеры будут его использовать. Они будут использовать любой язык, который справляется с задачей. Все, что нам нужно сделать, — это позаботиться о том, чтобы этот новый Lisp решал какую-то важную задачу лучше других языков.
История вселяет определенный оптимизм. Со временем каждый новый язык программирования перенимал все больше и больше возможностей из Lisp. Осталось скопировать совсем немного, прежде чем созданный вами язык превратится в Lisp. Последний популярный язык, Python, — это разбавленный Lisp с инфиксным синтаксисом и без макросов. Новый Lisp стал бы естественным шагом в этом развитии.
Иногда мне кажется, что было бы хорошим маркетинговым ходом назвать его улучшенной версией Python. Это звучит моднее, чем Lisp. Для многих людей Lisp — это медленный язык искусственного интеллекта с кучей скобок. Официальная биография Фрица Кунце тщательно избегает упоминания слова на букву «L». Но мне кажется, что нам не стоит бояться называть новый Lisp словом Lisp. Lisp до сих пор пользуется глубоким скрытым уважением среди лучших хакеров — тех, кто, например, прошел курс 6.001 и понял его. А это именно те пользователи, которых вам нужно завоевать.
В статье «Как стать хакером» Эрик Рэймонд описывает Lisp как нечто вроде латыни или древнегреческого — язык, который стоит выучить в качестве интеллектуального упражнения, даже если вы не будете использовать его на практике:
Lisp стоит изучить ради того глубокого чувства просветления, которое вы испытаете, когда наконец постигнете его суть; этот опыт сделает вас лучшим программистом на всю оставшуюся жизнь, даже если вы сами никогда не будете часто использовать Lisp.
Если бы я не знал Lisp, чтение этих строк заставило бы меня задаться вопросами. Язык, который сделает меня лучшим программистом, — если эти слова вообще имеют смысл, — это язык, который должен быть лучше для программирования. И именно это, по сути, вытекает из слов Эрика.
Пока эта идея витает в воздухе, я думаю, хакеры будут достаточно восприимчивы к новому Lisp, даже если он будет называться Lisp. Но этот Lisp должен быть языком для хакеров, подобно классическим Lisp 1970-х годов. Он должен быть лаконичным, простым и приспособленным для хакерства. И он должен обладать мощными библиотеками для того, чем хакеры хотят заниматься сейчас.
Что касается библиотек, я думаю, есть возможность обыграть такие языки, как Perl и Python, на их собственном поле. Множество новых приложений, которые потребуется написать в ближайшие годы, будут серверными приложениями. Нет никаких причин, почему бы новому Lisp не иметь библиотек для работы со строками, не уступающих Perl, и если бы в этом новом Lisp также появились мощные библиотеки для серверных приложений, он мог бы стать очень популярным. Настоящие хакеры не станут воротить нос от нового инструмента, который позволит им решать сложные задачи с помощью нескольких вызовов библиотек. Помните: хакеры ленивы.
Еще большим выигрышем могла бы стать поддержка серверных приложений на уровне ядра языка. Например, явная поддержка программ с множеством пользователей или владение данными на уровне тегов типов.
Серверные приложения также дают нам ответ на вопрос о том, для чего этот новый Lisp будет применяться. Не помешало бы сделать Lisp более удобным в качестве скриптового языка для Unix. (Сделать его хуже было бы трудно.) Но мне кажется, есть области, где потеснить существующие языки было бы проще. По-моему, лучше было бы последовать модели Tcl и поставлять Lisp вместе с законченной системой для поддержки серверных приложений. Lisp естественным образом подходит для серверных приложений. Лексические замыкания позволяют добиться эффекта подпрограмм, когда пользовательский интерфейс представляет собой просто череду веб-страниц. S-выражения отлично ложатся на html, а макросы прекрасно подходят для его генерации. Нужны более совершенные инструменты для написания серверных приложений, и нужен новый Lisp — и то и другое отлично подошло бы друг другу.
12 Язык мечты
Подводя итог, давайте попробуем описать язык мечты хакера. Язык мечты — красивый, чистый и лаконичный. У него есть интерактивная командная строка (toplevel), которая быстро запускается. Вы можете писать программы для решения типичных задач совсем небольшим объемом кода. Почти весь код в любой написанной вами программе специфичен именно для вашего приложения. Все остальное уже сделано за вас.
Синтаксис языка предельно краток. Вам никогда не приходится вводить лишний символ или даже часто использовать клавишу Shift.
Используя крупные абстракции, вы можете очень быстро написать первую версию программы. Позже, когда вы захотите провести оптимизацию, появится отличный профилировщик, который подскажет, на чем сосредоточить внимание. Вы можете сделать внутренние циклы молниеносно быстрыми, при необходимости даже вставляя инлайновый байт-код.
Есть множество хороших примеров для подражания, и язык настолько интуитивно понятен, что научиться им пользоваться по примерам можно за пару минут. Вам не нужно часто заглядывать в руководство. Руководство тонкое, и в нем мало предостережений и оговорок.
У языка компактное ядро и мощные, в высокой степени ортогональные библиотеки, спроектированные столь же тщательно, как и само ядро. Все библиотеки отлично сочетаются друг с другом; все в языке пригнано друг к другу, как детали в хорошем фотоаппарате. Ничто не помечено как устаревшее и не сохраняется лишь ради обратной совместимости. Исходный код всех библиотек легкодоступен. Язык легко взаимодействует с операционной системой и приложениями, написанными на других языках.
Язык построен послойно. Абстракции более высокого уровня предельно прозрачным образом строятся из абстракций более низкого уровня, до которых вы всегда можете добраться при желании.
От вас не скрыто ничего, кроме того, что абсолютно необходимо скрыть. Язык предлагает абстракции исключительно для того, чтобы сберечь ваши силы, а не для того, чтобы указывать вам, что делать. Более того, язык побуждает вас быть равноправным участником его проектирования. Вы можете изменить в нем абсолютно все, включая даже его синтаксис, и все, что вы пишете, обладает, насколько это возможно, тем же статусом, что и предопределенные элементы языка.
Примечания
[1] Макросы, очень близкие к современной концепции, были предложены Тимоти Хартом в 1964 году, через два года после выпуска Lisp 1.5. Изначально недоставало способов избежать захвата переменных и повторного вычисления; примеры Харта страдали обоими этими недостатками.
[2] В книге Когда воздух касается твоего мозга нейрохирург Фрэнк Вертосик пересказывает разговор, в котором его старший ординатор Гэри рассуждает о разнице между хирургами и терапевтами («блохами»):
Мы с Гэри заказали большую пиццу и нашли свободную кабинку. Шеф закурил сигарету. «Посмотри на этих чертовых блох, они без умолку треплются о какой-то болезни, которую увидят раз в жизни. В этом вся беда блох — им подавай только всякую диковинку. Свои рядовые случаи они ненавидят. Вот в чем разница между нами и гребаными блохами. Понимаешь, мы любим хорошие сочные грыжи поясничных дисков, а они ненавидят гипертонию...»
Трудно представить поясничную грыжу сочной (разве что в буквальном смысле). И все же, мне кажется, я понимаю, что они имеют в виду. Мне самому часто попадались «сочные» баги, которые так и тянуло выследить. Человеку не из сферы программирования трудно вообразить, что в баге можно находить удовольствие. Наверняка лучше, когда все просто работает. С одной стороны, так и есть. И тем не менее, в охоте за определенного рода ошибками определенно присутствует мрачное удовлетворение.