Five Questions about Language Design
Пол Грэм делится заметками с панельной дискуссии 2001 года в MIT о проектировании языков программирования. Он формулирует пять ключевых тезисов: языки создаются для людей, а не для машин; проектировать нужно для себя и лучших программистов; программисту следует давать максимум контроля; краткость кода недооценена; программирование — это скорее архитектура, чем математика. Грэм также ставит открытые вопросы — об организации больших библиотек, префиксном синтаксисе, серверном ПО и поиске новых абстракций. Он предсказывает, что серверные приложения вернут моду на создание новых языков и сделают эффективность снова важной. Отдельно Грэм критикует объектно-ориентированное программирование как переоценённую парадигму и проектирование языков комитетами как неспособное на риск.
Май 2001
(Это заметки, которые я подготовил для панельной дискуссии о проектировании языков программирования в MIT 10 мая 2001 года.)
1. Языки программирования — для людей.
Языки программирования — это способ общения людей с компьютерами. Компьютеру было бы одинаково удобно говорить на любом однозначном языке. Высокоуровневые языки существуют потому, что люди не способны работать с машинным кодом. Смысл языков программирования — уберечь наш хрупкий человеческий мозг от того, чтобы его захлестнула масса деталей.
Архитекторы знают, что одни задачи проектирования более «человечны», чем другие. Одна из самых чистых и абстрактных задач — проектирование мостов. Там ваша работа по большей части сводится к тому, чтобы перекрыть заданное расстояние минимумом материала. На другом конце спектра — проектирование стульев. Дизайнерам стульев приходится постоянно думать о человеческих задницах.
С софтом дело обстоит точно так же. Проектирование алгоритмов маршрутизации данных в сети — красивая абстрактная задача, как проектирование мостов. А вот проектирование языков программирования — это как проектирование стульев: сплошная работа с человеческими слабостями.
Большинству из нас неприятно это признавать. Проектирование систем с элегантной математикой звучит куда привлекательнее, чем потакание человеческим слабостям. И у математической элегантности есть своя роль: некоторые виды элегантности делают программы понятнее. Но элегантность — не самоцель.
И когда я говорю, что языки должны проектироваться с учётом человеческих слабостей, я не имею в виду, что их нужно проектировать для плохих программистов. На самом деле я считаю, что проектировать следует для лучших программистов, но даже у лучших программистов есть ограничения. Вряд ли кому-то понравилось бы программировать на языке, где все переменные — буква x с целочисленными индексами.
2. Проектируй для себя и своих друзей.
Если взглянуть на историю языков программирования, многие из лучших языков были созданы авторами для собственного использования, а многие из худших — для использования другими людьми.
Когда языки проектируются для других, это всегда конкретная группа других: люди, не такие умные, как создатель языка. В итоге получается язык, который разговаривает с вами свысока. Cobol — крайний случай, но этим духом пронизаны многие языки.
Это не имеет отношения к уровню абстракции языка. C — довольно низкоуровневый язык, но он был создан авторами для собственного использования, и именно поэтому хакеры его любят.
Аргумент в пользу проектирования языков для плохих программистов состоит в том, что плохих программистов больше, чем хороших. Возможно, это и так. Но те немногие хорошие программисты пишут непропорционально большую долю всего софта.
Мне интересен вопрос: как спроектировать язык, который понравится лучшим из хакеров? Я склонен думать, что это тот же вопрос, что и «как спроектировать хороший язык программирования?», но даже если это не так — это как минимум интересный вопрос.
3. Дай программисту максимум контроля.
Многие языки (особенно те, что спроектированы для других) ведут себя как гувернантка: пытаются не дать вам делать то, что, по их мнению, вам вредно. Мне нравится противоположный подход: дайте программисту столько контроля, сколько возможно.
Когда я впервые изучил Lisp, больше всего мне понравилось то, что он считал меня равным партнёром. Во всех других языках, которые я знал до этого, были язык и моя программа, написанная на этом языке, и они были чётко разделены. Но в Lisp функции и макросы, которые я писал, ничем не отличались от тех, из которых состоял сам язык. Я мог переписать язык, если хотел. В этом была та же притягательность, что и в open-source софте.
4. Стремись к краткости.
Краткость недооценивают и даже презирают. Но если заглянуть в душу хакерам, вы увидите, что они её по-настоящему любят. Сколько раз вы слышали, как хакеры с теплотой рассказывали о том, как, скажем, в APL они могли творить невероятные вещи парой строк кода? Я думаю, что на всё, что по-настоящему нравится по-настоящему умным людям, стоит обращать внимание.
Я считаю, что почти всё, что можно сделать для сокращения программ — это благо. Библиотечных функций должно быть много; всё, что может быть неявным, должно быть неявным; синтаксис должен быть лаконичным до крайности; даже имена сущностей должны быть короткими.
И короткими должны быть не только программы. Руководство тоже должно быть тонким. Значительная часть документации занята уточнениями, оговорками, предупреждениями и описанием особых случаев. Если заставить себя сократить руководство, в лучшем случае вы сделаете это, исправив в языке те вещи, которые требовали столь подробных объяснений.
5. Признай, чем является хакерство.
Многие хотят, чтобы хакерство было математикой, или хотя бы чем-то вроде естественной науки. Я думаю, хакерство больше похоже на архитектуру. Архитектура связана с физикой в том смысле, что архитекторы должны проектировать здания, которые не рушатся, но подлинная цель архитекторов — создавать великие здания, а не делать открытия в области статики.
Хакеры любят создавать великие программы. И я думаю, по крайней мере в собственных глазах, мы должны помнить, что написать великую программу — достойное дело, даже если эта работа не конвертируется легко в общепринятую интеллектуальную валюту научных статей. С интеллектуальной точки зрения спроектировать язык, который полюбят программисты, столь же ценно, как и спроектировать ужасный язык, воплощающий какую-нибудь идею, о которой можно написать статью.
1. Как организовать большие библиотеки?
Библиотеки становятся всё более важной частью языков программирования. Они также становятся больше, и это может быть опасно. Если поиск нужной библиотечной функции занимает больше времени, чем написание её с нуля, весь этот код лишь делает ваше руководство толще. (Документация Symbolics была наглядным примером.) Поэтому я думаю, что нам придётся работать над способами организации библиотек. В идеале их нужно проектировать так, чтобы программист мог угадать, какой вызов библиотеки сделает то, что нужно.
2. Люди действительно боятся префиксного синтаксиса?
Это открытый вопрос в том смысле, что я думаю о нём годами и до сих пор не знаю ответа. Префиксный синтаксис кажется мне совершенно естественным, разве что за исключением математики. Но может быть, значительная часть непопулярности Lisp объясняется просто непривычным синтаксисом. Стоит ли что-то с этим делать, если это правда, — отдельный вопрос.
3. Что нужно для серверного софта?
Я думаю, что многие из самых захватывающих новых приложений, которые будут написаны в ближайшие двадцать лет, будут веб-приложениями — программами, которые работают на сервере и общаются с вами через браузер. И для написания таких программ нам, возможно, понадобятся новые вещи.
Одна из них — поддержка нового способа выпуска серверных приложений. Вместо одного-двух крупных релизов в год, как у десктопного софта, серверные приложения выпускаются серией мелких изменений. У вас может быть пять-десять релизов в день. И, как правило, все всегда используют последнюю версию.
Вы знаете, как можно проектировать программы так, чтобы их было удобно отлаживать? Так вот, серверный софт точно так же должен проектироваться так, чтобы его было удобно изменять. Нужна возможность легко вносить изменения или хотя бы понимать, какое изменение незначительно, а какое — судьбоносно.
Ещё одна вещь, которая может оказаться полезной для серверного софта, — как ни удивительно, продолжения (continuations). В веб-приложениях можно использовать нечто вроде стиля передачи продолжений, чтобы получить эффект подпрограмм в изначально не хранящем состояния мире веб-сессии. Возможно, стоило бы иметь настоящие продолжения, если это не слишком дорого.
4. Какие новые абстракции ещё предстоит открыть?
Я не уверен, насколько это реалистичная надежда, но лично мне очень хотелось бы открыть новую абстракцию — нечто, что дало бы такой же скачок, как функции первого класса, рекурсия или даже именованные параметры. Возможно, это несбыточная мечта. Такие вещи открывают нечасто. Но я всегда в поиске.
1. Вы можете использовать любой язык.
Написание прикладных программ раньше означало написание десктопного софта. А в десктопном софте есть сильный крен в сторону написания приложений на том же языке, что и операционная система. Поэтому десять лет назад писать софт означало, по сути, писать софт на C. Со временем сложилась традиция: прикладные программы нельзя писать на необычных языках. И эта традиция формировалась так долго, что её усвоили даже нетехнические люди — менеджеры и венчурные капиталисты.
Серверный софт полностью ломает эту модель. С серверным софтом можно использовать любой язык. Почти никто этого пока не понимает (особенно менеджеры и венчурные капиталисты). Несколько хакеров это понимают, и именно поэтому мы вообще слышим о новых независимых языках вроде Perl и Python. Мы слышим о Perl и Python не потому, что на них пишут приложения для Windows.
Для нас, людей, интересующихся проектированием языков программирования, это означает, что теперь у нашей работы потенциально есть реальная аудитория.
2. Скорость обеспечивают профилировщики.
Создатели языков, или по крайней мере их реализаторы, любят писать компиляторы, генерирующие быстрый код. Но я не думаю, что именно это делает языки быстрыми для пользователей. Кнут давно указал, что скорость имеет значение лишь в нескольких критических узких местах. И любой, кто пробовал, знает, что угадать, где эти узкие места, невозможно. Ответ — профилировщики.
Создатели языков решают не ту задачу. Пользователям не нужно, чтобы бенчмарки выполнялись быстро. Им нужен язык, который покажет, какие части их собственных программ нужно переписать. Вот откуда берётся скорость на практике. Так что, возможно, чистым выигрышем было бы, если бы реализаторы языков потратили половину времени, отведённого на оптимизации компилятора, на написание хорошего профилировщика.
3. Для проектирования языка нужно приложение, которое его двигает.
Возможно, это не абсолютное правило, но похоже, что лучшие языки эволюционировали вместе с каким-то приложением, для написания которого они использовались. C был написан людьми, которым он был нужен для системного программирования. Lisp разрабатывался отчасти для символьного дифференцирования, и McCarthy так рвался начать, что писал программы для дифференцирования уже в первой статье о Lisp в 1960 году.
Особенно хорошо, если ваше приложение решает какую-то новую задачу. Это подтолкнёт ваш язык к появлению новых возможностей, нужных программистам. Лично меня интересует создание языка, который будет хорош для написания серверных приложений.
[Во время дискуссии Guy Steele также высказал эту мысль, добавив, что приложением не должно быть написание компилятора для вашего языка — если только ваш язык не предназначен специально для написания компиляторов.]
4. Язык должен быть хорош для написания одноразовых программ.
Вы знаете, что такое одноразовая программа: нечто, что вы быстро пишете для какой-то ограниченной задачи. Думаю, если оглядеться, вы обнаружите, что многие большие серьёзные программы начинались как одноразовые. Я бы не удивился, если бы большинство программ начинались как одноразовые. А значит, если вы хотите создать язык, хороший для написания софта вообще, он должен быть хорош для написания одноразовых программ, потому что это личиночная стадия большинства программ.
5. Синтаксис связан с семантикой.
Традиционно считается, что синтаксис и семантика полностью независимы. Это прозвучит шокирующе, но, возможно, это не так. Я думаю, что то, что вы хотите иметь в языке, может быть связано с тем, как вы это выражаете.
Недавно я разговаривал с Robert Morris, и он заметил, что перегрузка операторов даёт больше выигрыша в языках с инфиксным синтаксисом. В языке с префиксным синтаксисом любая определённая вами функция фактически является оператором. Если вы хотите определить сложение для нового типа чисел, который вы придумали, можно просто определить новую функцию. Если же вы делаете это в языке с инфиксным синтаксисом, использование перегруженного оператора и вызов функции выглядят совершенно по-разному.
1. Новые языки программирования.
В 1970-х было модно проектировать новые языки программирования. В последнее время — нет. Но я думаю, серверный софт снова сделает новые языки модными. С серверным софтом можно использовать любой язык, и если кто-то создаст язык, который действительно окажется лучше имеющихся, найдутся люди, готовые рискнуть и использовать его.
2. Разделение времени.
Richard Kelsey назвал это идеей, время которой снова пришло, на предыдущей панели, и я полностью с ним согласен. Моё предположение (и, похоже, предположение Microsoft) — что значительная часть вычислений переместится с десктопов на удалённые серверы. Иными словами, разделение времени возвращается. И я думаю, что потребуется поддержка этого на уровне языка. Например, я знаю, что Richard и Jonathan Rees проделали большую работу по реализации планирования процессов внутри Scheme 48.
3. Эффективность.
В последнее время начинало казаться, что компьютеры наконец стали достаточно быстрыми. Всё чаще мы слышали о байт-коде, что, по крайней мере для меня, означает, что мы чувствуем запас вычислительных циклов. Но я не думаю, что так будет с серверным софтом. Кто-то должен будет платить за серверы, на которых работает программа, и количество пользователей, которых можно обслужить на одной машине, будет делителем капитальных затрат.
Так что я думаю, эффективность будет важна — по крайней мере в вычислительных узких местах. Особенно важно будет делать быстрый ввод-вывод, потому что серверные приложения выполняют очень много операций ввода-вывода.
Может оказаться, что байт-код в итоге не будет выигрышем. Sun и Microsoft, похоже, сейчас сошлись в своего рода битве байт-кодов. Но они делают это потому, что байт-код — удобное место, чтобы встроиться в процесс, а не потому, что байт-код сам по себе хорошая идея. Может оказаться, что всё это поле битвы окажется в стороне от магистрали. Это было бы довольно забавно.
1. Клиенты.
Это лишь предположение, но я полагаю, что выигрышной моделью для большинства приложений будет чисто серверная. Проектировать софт, исходя из предположения, что у всех будет ваш клиент, — это как проектировать общество, исходя из предположения, что все будут просто честными. Это было бы, конечно, удобно, но приходится допускать, что этого никогда не произойдёт.
Я думаю, появится множество устройств с тем или иным доступом к вебу, и единственное, что можно будет о них предполагать, — это поддержка простого HTML и форм. Будет ли у вас браузер на мобильном телефоне? Будет ли телефон в вашем Palm Pilot? Станет ли экран вашего BlackBerry больше? Сможете ли вы выходить в интернет с Game Boy? С часов? Не знаю. И мне не нужно знать, если я ставлю на то, что всё будет на сервере. Это просто гораздо надёжнее — держать все мозги на сервере.
2. Объектно-ориентированное программирование.
Понимаю, что это спорный тезис, но я не считаю объектно-ориентированное программирование чем-то таким уж значительным. Я думаю, это хорошая модель для определённых видов приложений, которым нужна именно такая структура данных, — оконных систем, симуляций, CAD-программ. Но я не вижу, почему это должно быть моделью для всего программирования.
Я думаю, одна из причин, по которой люди в больших компаниях любят объектно-ориентированное программирование, — оно порождает массу того, что выглядит как работа. Нечто, что естественно можно было бы представить, скажем, списком целых чисел, теперь может быть представлено классом со всевозможными подпорками и суетой.
Ещё одна привлекательная сторона объектно-ориентированного программирования — методы дают некоторый эффект функций первого класса. Но для программистов на Lisp это давно не новость. Когда у вас есть настоящие функции первого класса, вы просто используете их так, как уместно для текущей задачи, вместо того чтобы запихивать всё в форму классов и методов.
Для проектирования языков это, на мой взгляд, означает, что не следует встраивать объектно-ориентированное программирование слишком глубоко. Возможно, ответ — предоставить более общие базовые механизмы и позволить людям проектировать любые объектные системы в виде библиотек.
3. Проектирование комитетом.
Когда ваш язык проектируется комитетом — это большая ловушка, и не только по тем причинам, о которых все знают. Все знают, что комитеты порождают громоздкие, непоследовательные решения. Но я думаю, большая опасность в том, что они не будут рисковать. Когда за дело отвечает один человек, он может идти на риски, на которые комитет никогда не согласится.
Но необходимо ли рисковать, чтобы создать хороший язык? Многие могут подозревать, что проектирование языков — это область, где стоит держаться довольно близко к общепринятым взглядам. Уверен, это не так. Во всём, чем занимаются люди, вознаграждение пропорционально риску. Почему проектирование языков должно быть исключением?