Being Popular
Эссе Пола Грэма (май 2001), задуманное как бизнес-план для нового языка программирования (будущего Arc). Грэм утверждает: язык не обязан быть хорошим, чтобы стать популярным, но он обязан быть популярным, чтобы быть хорошим, — потому что развитие и улучшения приходят от реальных требовательных пользователей. Главными судьями языков он считает не обычных программистов, а узкое меньшинство экспертов-хакеров, которые пишут весь хороший софт и влияют на выбор остальных. Чтобы язык полюбили хакеры, ему нужны лаконичность (краткость), возможность лезть внутрь и нарушать модель («hackability»), пригодность для одноразовых программ, мощные библиотеки, хороший профайлер вместо ставки на скорость компиляции и время на органический рост через ранних адоптеров. Грэм критикует Common Lisp за слабые библиотеки и отсутствие связи с ОС, противопоставляет органический рост (Unix, MacLisp) методу «большого взрыва» (Multics, Common Lisp) и доказывает, что есть надежда на новый Lisp — краткий, простой, «хакабельный» и заточенный под серверные приложения.
Май 2001
(Эта статья была написана как своего рода бизнес-план для нового языка. Поэтому в ней отсутствует (поскольку принимается как само собой разумеющееся) самая важная черта хорошего языка программирования: очень мощные абстракции.)
Один мой друг как-то сказал видному эксперту по операционным системам, что хочет спроектировать действительно хороший язык программирования. Эксперт ответил ему, что это пустая трата времени, что языки программирования становятся популярными или непопулярными не из-за своих достоинств, и потому, каким бы хорошим ни был его язык, им никто не воспользуется. По крайней мере, именно так произошло с языком, который спроектировал он сам.
Что же делает язык популярным? Заслуживают ли популярные языки своей популярности? Стоит ли вообще пытаться определить, что такое хороший язык программирования? И как бы вы это сделали?
Я думаю, ответы на эти вопросы можно найти, если посмотреть на хакеров и понять, чего они хотят. Языки программирования существуют для хакеров, и язык программирования хорош как язык программирования (а не как, скажем, упражнение в денотационной семантике или проектировании компиляторов) тогда и только тогда, когда он нравится хакерам.
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, диалект Lisp от MIT, называвшийся MacLisp, был одним из немногих языков программирования, которыми серьёзный хакер вообще захотел бы пользоваться.
Сегодня Lisp — скриптовый язык двух умеренно популярных систем, Emacs и Autocad, и по этой причине я подозреваю, что бóльшая часть программирования на Lisp, которая ведётся сегодня, делается на Emacs Lisp или AutoLisp.
Языки программирования не существуют в изоляции. «Хакать» — переходный глагол: хакеры обычно что-то хакают, — и на практике языки оцениваются относительно того, для хака чего они используются. Так что если вы хотите спроектировать популярный язык, вам нужно либо поставлять нечто большее, чем просто язык, либо проектировать свой язык так, чтобы он заменил скриптовый язык какой-нибудь уже существующей системы.
Common Lisp непопулярен отчасти потому, что он — сирота. Изначально он действительно поставлялся с системой, которую можно было хакать: Lisp Machine. Но 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. Если что, это, возможно, помогло взрастить культ 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 можно было бы в него включить. Было бы хорошо, если бы макросы могли генерировать спецификаторы format так же, как они генерируют любой другой код.
Один видный хакер Lisp сказал мне, что его экземпляр CLTL раскрывается на разделе format. У меня тоже. Это, вероятно, указывает на пространство для улучшений. Это также может означать, что программы делают много ввода-вывода.
8 Эффективность
Хороший язык, как всем известно, должен генерировать быстрый код. Но на практике я не думаю, что быстрый код берётся в первую очередь из того, что вы делаете при проектировании языка. Как давным-давно указал Knuth, скорость имеет значение лишь в определённых критических узких местах. И, как с тех пор замечали многие программисты, очень часто ошибаешься в том, где эти узкие места.
Так что на практике способ получить быстрый код — иметь очень хороший профайлер, а не, скажем, делать язык строго типизированным. Вам не нужно знать тип каждого аргумента в каждом вызове в программе. Вам нужно иметь возможность объявлять типы аргументов в узких местах. И, что ещё важнее, вам нужно иметь возможность узнать, где эти узкие места.
Одна из претензий к Lisp состоит в том, что трудно понять, что обходится дорого. Это, возможно, правда. Это, возможно, также неизбежно, если вы хотите иметь очень абстрактный язык. И в любом случае я думаю, что хорошее профилирование сильно продвинуло бы решение этой проблемы: вы бы быстро узнали, что обходится дорого.
Часть проблемы здесь — социальная. Проектировщики языков любят писать быстрые компиляторы. Именно так они меряют своё мастерство. Профайлер они считают, в лучшем случае, дополнением. Но на практике хороший профайлер может сделать для скорости реальных программ, написанных на языке, больше, чем компилятор, генерирующий быстрый код. Здесь, опять же, проектировщики языков несколько оторваны от своих пользователей. Они действительно хорошо решают слегка не ту задачу.
Возможно, была бы хорошая идея иметь активный профайлер — проталкивать данные о производительности программисту, а не ждать, пока он сам придёт за ними. Например, редактор мог бы подсвечивать узкие места красным, когда программист редактирует исходный код. Другой подход — как-то представлять, что происходит в работающих программах. Это был бы особенно большой выигрыш в серверных приложениях, где у вас есть множество работающих программ для наблюдения. Активный профайлер мог бы графически показывать, что происходит в памяти по мере работы программы, или даже издавать звуки, сообщающие, что происходит.
Звук — хорошая подсказка о проблемах. В одном месте, где я работал, у нас была большая панель циферблатов, показывающая, что происходит с нашими веб-серверами. Стрелки двигались маленькими сервомоторами, которые издавали лёгкий шум, когда поворачивались. Я не мог видеть панель со своего стола, но обнаружил, что по звуку могу сразу понять, когда с сервером возникала проблема.
Возможно, удалось бы даже написать профайлер, который автоматически выявлял бы неэффективные алгоритмы. Я бы не удивился, если бы определённые паттерны обращения к памяти оказались верными признаками плохих алгоритмов. Если бы внутри компьютера бегал маленький человечек, исполняющий наши программы, у него, вероятно, нашлась бы такая же длинная и жалобная повесть о своей работе, как у служащего федерального правительства. У меня часто бывает ощущение, что я отправляю процессор в кучу погонь за дикими гусями, но у меня никогда не было хорошего способа посмотреть, чем он занимается.
Ряд Lisp'ов теперь компилируется в байт-код, который затем исполняется интерпретатором. Обычно это делается, чтобы облегчить портирование реализации, но это могло бы быть полезной чертой языка. Возможно, была бы хорошая идея сделать байт-код официальной частью языка и позволить программистам использовать встроенный (inline) байт-код в узких местах. Тогда такие оптимизации тоже были бы переносимыми.
Природа скорости, как её воспринимает конечный пользователь, может меняться. С ростом серверных приложений всё больше и больше программ могут оказаться ограничены по вводу-выводу (i/o-bound). Стоит делать ввод-вывод быстрым. Язык может помочь как простыми мерами вроде простых, быстрых функций форматированного вывода, так и глубокими структурными изменениями вроде кэширования и устойчивых (persistent) объектов.
Пользователей интересует время отклика. Но другой вид эффективности будет приобретать всё большее значение: число одновременных пользователей, которое вы можете обслуживать на один процессор. Многие из интересных приложений, которые будут написаны в ближайшем будущем, будут серверными, и число пользователей на сервер — критический вопрос для всякого, кто хостит такие приложения. В капитальных затратах бизнеса, предлагающего серверное приложение, это знаменатель.
Годами эффективность мало что значила в большинстве пользовательских приложений. Разработчики могли исходить из того, что у каждого пользователя на столе будет всё более мощный процессор. И по закону Паркинсона софт раздувался, чтобы использовать доступные ресурсы. С серверными приложениями это изменится. В этом мире железо и софт будут поставляться вместе. Для компаний, предлагающих серверные приложения, очень большая разница для итоговой прибыли — сколько пользователей они смогут обслуживать на один сервер.
В некоторых приложениях ограничивающим фактором будет процессор, и скорость выполнения будет самой важной вещью для оптимизации. Но часто пределом будет память; число одновременных пользователей будет определяться объёмом памяти, нужным под данные каждого пользователя. Язык может помочь и здесь. Хорошая поддержка потоков позволит всем пользователям делить одну кучу (heap). Также может помочь наличие устойчивых объектов и/или поддержки ленивой загрузки на уровне языка.
9 Время
Последний ингредиент, нужный популярному языку, — время. Никто не хочет писать программы на языке, который может исчезнуть, как исчезает столь много языков программирования. Так что большинство хакеров склонны выжидать, пока язык не просуществует пару лет, прежде чем хотя бы задуматься о том, чтобы им воспользоваться.
Изобретатели чудесных новых вещей часто с удивлением обнаруживают это, но вам нужно время, чтобы донести любую мысль до людей. Один мой друг редко делает что-либо, когда его просят в первый раз. Он знает, что люди иногда просят о том, чего, как потом выясняется, на самом деле не хотят. Чтобы не тратить своё время впустую, он ждёт до третьего или четвёртого раза, когда его о чём-то попросят; к тому времени тот, кто просит, может быть изрядно раздражён, но зато он, вероятно, действительно хочет того, о чём просит.
Большинство людей научились применять похожего рода фильтрацию к новым вещам, о которых слышат. Они даже не начинают обращать внимание, пока не услышат о чём-то раз десять. И они совершенно правы: большинство модных новых «чего бы то ни было» действительно оказываются пустой тратой времени и со временем исчезают. Откладывая изучение VRML, я избежал того, чтобы вообще его изучать.
Так что всякий, кто изобретает что-то новое, должен ожидать, что ему придётся повторять своё сообщение годами, прежде чем люди начнут его понимать. Мы написали то, что, насколько мне известно, было первым серверным веб-приложением, и нам потребовались годы, чтобы донести до людей, что его не нужно скачивать. Дело не в том, что они были глупы. Они просто пропускали нас мимо ушей.
Хорошая новость в том, что простое повторение решает проблему. Всё, что вам нужно, — продолжать рассказывать свою историю, и в конце концов люди начнут слышать. Внимание обращают не тогда, когда замечают, что ты здесь; а тогда, когда замечают, что ты всё ещё здесь.
Оно и к лучшему, что набрать обороты обычно занимает некоторое время. Большинство технологий изрядно эволюционируют даже после первого запуска — особенно языки программирования. Ничего не может быть лучше для новой технологии, чем несколько лет использования лишь небольшим числом ранних адоптеров. Ранние адоптеры искушённы и требовательны, и быстро вымывают любые недостатки, какие остались в вашей технологии. Когда у вас лишь несколько пользователей, вы можете быть в тесном контакте со всеми ними. И ранние адоптеры снисходительны, когда вы улучшаете свою систему, даже если это что-то ломает.
Есть два способа, которыми внедряется новая технология: метод органического роста и метод большого взрыва. Метод органического роста олицетворяется классическим, действующим по наитию, недофинансированным гаражным стартапом. Пара ребят, работая в безвестности, разрабатывают какую-то новую технологию. Они запускают её без всякого маркетинга и поначалу имеют лишь нескольких (фанатично преданных) пользователей. Они продолжают улучшать технологию, а тем временем их пользовательская база растёт из уст в уста. Не успеют они оглянуться, как они уже большие.
Другой подход, метод большого взрыва, олицетворяется стартапом с венчурными деньгами и мощным маркетингом. Они спешат разработать продукт, запускают его с большой шумихой и сразу (как они надеются) получают большую пользовательскую базу.
Как правило, гаражные ребята завидуют ребятам большого взрыва. Ребята большого взрыва гладкие, уверенные и уважаемые венчурными инвесторами. Они могут позволить себе всё самое лучшее, а PR-кампания вокруг запуска имеет побочный эффект — делает из них знаменитостей. Ребята органического роста, сидя в своём гараже, чувствуют себя бедными и нелюбимыми. И всё же я думаю, что они часто напрасно жалеют себя. Органический рост, похоже, даёт лучшую технологию и более богатых основателей, чем метод большого взрыва. Если вы посмотрите на доминирующие сегодня технологии, вы обнаружите, что большинство из них выросли органически.
Этот паттерн применим не только к компаниям. Вы видите его и в финансируемых исследованиях. Multics и Common Lisp были проектами большого взрыва, а Unix и MacLisp — проектами органического роста.
10 Перепроектирование
«Лучшее письмо — это переписывание», — написал E. B. White. Каждый хороший писатель это знает, и для софта это тоже верно. Важнейшая часть проектирования — перепроектирование. Языки программирования, в особенности, перепроектируют недостаточно.
Чтобы писать хороший софт, вам нужно одновременно удерживать в голове две противоположные идеи. Вам нужна наивная вера юного хакера в свои способности и в то же время скептицизм ветерана. Вы должны уметь думать да насколько это может быть сложно? одной половиной мозга, думая при этом это никогда не сработает другой.
Фокус в том, чтобы осознать, что никакого реального противоречия здесь нет. Вы хотите быть оптимистом и скептиком в отношении двух разных вещей. Вы должны быть оптимистичны насчёт возможности решить задачу, но скептичны насчёт ценности того решения, что у вас есть на данный момент.
Люди, которые делают хорошую работу, часто считают, что то, над чем они работают, никуда не годится. Другие видят то, что они сделали, и полны восхищения, а творец полон тревоги. Этот паттерн не случаен: именно тревога сделала работу хорошей.
Если вы можете удерживать надежду и тревогу в равновесии, они будут двигать проект вперёд так же, как две ваши ноги двигают вперёд велосипед. В первой фазе двухтактного двигателя инноваций вы яростно работаете над какой-то проблемой, вдохновлённые уверенностью, что сумеете её решить. Во второй фазе вы смотрите на то, что сделали, в холодном свете утра и очень ясно видите все недостатки. Но пока ваш критический дух не перевешивает вашу надежду, вы сможете взглянуть на свою, признаться, незавершённую систему и подумать: «да насколько сложно пройти оставшийся путь?» — тем самым продолжая цикл.
Удерживать эти две силы в равновесии — дело хитрое. У юных хакеров преобладает оптимизм. Они что-то выпускают, убеждены, что это великолепно, и никогда это не улучшают. У старых хакеров преобладает скептицизм, и они даже не осмеливаются браться за амбициозные проекты.
Всё, что вы можете сделать, чтобы поддерживать цикл перепроектирования, — хорошо. Прозу можно переписывать снова и снова, пока вы ею не довольны. Но софт, как правило, перепроектируют недостаточно. У прозы есть читатели, а у софта — пользователи. Если писатель переписывает эссе, люди, читавшие старую версию, вряд ли пожалуются, что их мысли сломала какая-то вновь введённая несовместимость.
Пользователи — обоюдоострый меч. Они могут помочь вам улучшить язык, но могут и отвадить вас от его улучшения. Так что выбирайте пользователей тщательно и не спешите наращивать их число. Иметь пользователей — это как оптимизация: мудрый путь в том, чтобы её откладывать. К тому же, как правило, в любой данный момент вам может сойти с рук больше изменений, чем вы думаете. Вводить изменения — это как срывать пластырь: боль становится воспоминанием почти в тот же миг, как вы её почувствуете.
Все знают, что плохая идея — поручать проектирование языка комитету. Комитеты дают плохой дизайн. Но я думаю, что худшая опасность комитетов в том, что они мешают перепроектированию. Внести изменения — столько труда, что никто не хочет с этим возиться. То, что решил комитет, как правило, таким и остаётся, даже если большинству членов это не нравится.
Даже комитет из двух человек мешает перепроектированию. Это особенно происходит в интерфейсах между кусками софта, написанными двумя разными людьми. Чтобы изменить интерфейс, оба должны согласиться изменить его одновременно. И потому интерфейсы, как правило, вообще не меняются, что является проблемой, потому что они, как правило, — одна из самых ситуативных (ad hoc) частей любой системы.
Одно из решений здесь — проектировать системы так, чтобы интерфейсы были горизонтальными, а не вертикальными, — чтобы модули всегда были вертикально уложенными слоями абстракции. Тогда интерфейс, как правило, будет принадлежать одному из них. Нижний из двух уровней либо будет языком, на котором написан верхний, — в этом случае интерфейс будет принадлежать нижнему уровню, — либо будет рабом, и тогда интерфейс может диктоваться верхним уровнем.
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 — это медленный язык для ИИ с кучей скобок. Официальная биография Fritz Kunze тщательно избегает упоминания слова на букву «L». Но я полагаю, что нам не стоит бояться назвать новый Lisp — Lisp'ом. Lisp по-прежнему пользуется немалым скрытым уважением среди самых лучших хакеров — например, тех, кто прослушал курс 6.001 и понял его. А это как раз те пользователи, которых вам нужно завоевать.
В «How to Become a Hacker» Eric Raymond описывает Lisp как нечто вроде латыни или греческого — язык, который вам следует выучить как интеллектуальное упражнение, даже если вы на самом деле не будете им пользоваться:
Lisp стоит выучить ради того глубокого опыта просветления, который вы переживёте, когда наконец его постигнете; этот опыт сделает вас лучшим программистом на всю оставшуюся жизнь, даже если вы сами никогда не будете много пользоваться самим Lisp.
Если бы я не знал Lisp, чтение этого побудило бы меня задавать вопросы. Язык, который сделал бы меня лучшим программистом, — если это вообще что-то значит — означает язык, который был бы лучше для программирования. И это и есть, по сути, подтекст того, что говорит Eric.
Пока эта идея всё ещё носится в воздухе, я думаю, хакеры будут достаточно восприимчивы к новому Lisp, даже если он называется Lisp. Но этот Lisp должен быть хакерским языком, как классические Lisp'ы 1970-х. Он должен быть лаконичным, простым и хакабельным. И у него должны быть мощные библиотеки для того, что хакеры хотят делать сейчас.
В вопросе библиотек, я думаю, есть пространство обыграть такие языки, как Perl и Python, на их же поле. Многие из новых приложений, которые понадобится написать в грядущие годы, будут серверными приложениями. Нет причин, по которым у нового Lisp не могло бы быть строковых библиотек не хуже, чем у Perl, и если бы у этого нового Lisp были ещё и мощные библиотеки для серверных приложений, он мог бы стать очень популярным. Настоящие хакеры не станут воротить нос от нового инструмента, который позволит им решать трудные задачи парой библиотечных вызовов. Помните, хакеры ленивы.
Ещё бóльшим выигрышем могла бы стать поддержка серверных приложений на уровне ядра языка. Например, явная поддержка программ с несколькими пользователями или владение данными на уровне тегов типов.
Серверные приложения дают нам и ответ на вопрос, что этот новый Lisp будет использоваться хакать. Не помешало бы сделать Lisp лучше как скриптовый язык для Unix. (Сделать его хуже было бы трудно.) Но я думаю, есть области, где существующие языки было бы легче обыграть. Я думаю, было бы лучше последовать модели Tcl и поставлять Lisp вместе с полной системой для поддержки серверных приложений. Lisp естественно подходит для серверных приложений. Лексические замыкания дают способ получить эффект подпрограмм, когда пользовательский интерфейс — это просто череда веб-страниц. S-выражения славно отображаются на HTML, а макросы хорошо его генерируют. Должны появиться лучшие инструменты для написания серверных приложений, и должен появиться новый Lisp, и они очень хорошо работали бы вместе.
12 Язык мечты
В качестве итога давайте попробуем описать язык мечты хакера. Язык мечты красив, чист и лаконичен. У него есть интерактивный верхний уровень (toplevel), который быстро запускается. Вы можете писать программы, решающие распространённые задачи, очень малым количеством кода. Почти весь код в любой программе, которую вы пишете, — это код, специфичный для вашего приложения. Всё остальное уже сделано за вас.
Синтаксис языка краток до предела. Вам никогда не приходится набирать лишний символ или даже часто пользоваться клавишей shift.
Используя крупные абстракции, вы можете очень быстро написать первую версию программы. Позже, когда вы захотите оптимизировать, есть по-настоящему хороший профайлер, который подсказывает, на чём сосредоточить внимание. Вы можете сделать внутренние циклы ослепительно быстрыми, при необходимости даже написав встроенный (inline) байт-код.
Есть множество хороших примеров, на которых можно учиться, и язык достаточно интуитивен, чтобы вы могли научиться им пользоваться по примерам за пару минут. Вам не нужно часто заглядывать в руководство. Руководство тонкое, и в нём мало предупреждений и оговорок.
У языка небольшое ядро и мощные, в высшей степени ортогональные библиотеки, спроектированные так же тщательно, как и само ядро языка. Все библиотеки хорошо работают вместе; всё в языке подогнано друг к другу, как детали в хорошей фотокамере. Ничто не объявлено устаревшим (deprecated) и не сохранено ради совместимости. Исходный код всех библиотек легко доступен. Легко общаться с операционной системой и с приложениями, написанными на других языках.
Язык построен слоями. Высокоуровневые абстракции построены очень прозрачным образом из низкоуровневых абстракций, до которых вы можете добраться, если захотите.
От вас не скрыто ничего, что не было бы абсолютно необходимо скрыть. Язык предлагает абстракции лишь как способ избавить вас от работы, а не как способ указывать вам, что делать. Более того, язык поощряет вас быть равноправным участником его проектирования. Вы можете изменить в нём всё, включая даже его синтаксис, и всё, что вы пишете, имеет, насколько это возможно, тот же статус, что и то, что идёт предопределённым.
Примечания
[1] Макросы, очень близкие к современной идее, были предложены Timothy Hart в 1964 году, через два года после выхода Lisp 1.5. Чего поначалу не хватало, так это способов избегать захвата переменных и многократного вычисления; примеры Hart подвержены и тому, и другому.
[2] В книге When the Air Hits Your Brain нейрохирург Frank Vertosick пересказывает разговор, в котором его старший ординатор Gary рассуждает о разнице между хирургами и терапевтами («блохами»):
Мы с Gary заказали большую пиццу и нашли свободную кабинку. Шеф закурил сигарету. «Посмотри на этих чёртовых блох, болтают о какой-то болезни, которую увидят раз в жизни. Вот в чём беда с блохами — им нравится только всё причудливое. Они терпеть не могут свои рутинные, хлеб-да-масло случаи. Вот в чём разница между нами и этими сраными блохами. Видишь ли, мы любим большие сочные грыжи поясничных дисков, а они ненавидят гипертонию...»
Трудно представить грыжу поясничного диска сочной (разве что буквально). И всё же, кажется, я понимаю, что они имеют в виду. У меня часто бывал сочный баг, который надо было выследить. Тому, кто не программист, было бы трудно вообразить, что в баге может быть удовольствие. Уж наверное лучше, когда всё просто работает. В одном смысле — да. И всё же есть неоспоримое мрачное удовлетворение в охоте за определёнными сортами багов.