pg.gimran.org

Краткость — это сила

Май 2002 · Все эссе

Перевод эссе Пола Грэма «Краткость — это сила». Оригинал: https://paulgraham.com/power.html. Машинный перевод (Gemini).

Translation of Paul Graham's essay 'Succinctness is Power'. Original: https://paulgraham.com/power.html. Machine translation (Gemini).

В дискуссии по поводу вопросов, поднятых в «Мести ботаников» в списке рассылки LL1, Пол Прескод написал то, что врезалось мне в память.

Цель Python — это регулярность и читаемость, а не краткость.

На первый взгляд, это звучит как довольно убийственная характеристика языка программирования. Насколько я могу судить, краткость = сила. Если так, сделав замену, мы получаем:

Цель Python — это регулярность и читаемость, а не сила.

и это вряд ли компромисс (если это вообще компромисс), на который захочется пойти. Это мало чем отличается от заявления, что цель Python — не быть эффективным в качестве языка программирования.

Верно ли, что краткость = сила? Мне кажется, это важный вопрос, возможно, самый важный для любого, кто интересуется дизайном языков, и было бы полезно взглянуть на него открыто. Я пока не до конца уверен, что ответ — однозначное «да», но для начала это кажется хорошей гипотезой.

Гипотеза

Моя гипотеза состоит в том, что краткость — это сила, или они достаточно близки, чтобы, за исключением патологических случаев, считать их идентичными.

Мне кажется, что краткость — это то, ради чего и существуют языки программирования. Компьютеры столь же охотно выполняли бы команды, отданные им напрямую на машинном языке. Я думаю, главная причина, по которой мы берем на себя труд разрабатывать языки высокого уровня, — это получение рычага, позволяющего нам выразить (и, что более важно, обдумать) в 10 строках на высокоуровневом языке то, что потребовало бы 1000 строк на машинном. Иными словами, главное предназначение языков высокого уровня — сделать исходный код меньше.

Если более компактный исходный код является целью языков высокого уровня, а сила чего-либо определяется тем, насколько хорошо оно достигает своей цели, то мерилом силы языка программирования является то, насколько маленькими он делает ваши программы.

И наоборот, язык, который не делает ваши программы маленькими, плохо справляется с тем, для чего языки программирования предназначены — словно нож, который плохо режет, или печать, которую невозможно разобрать.

Метрики

Маленькими, но в каком смысле? Самое распространенное мерило размера кода — это количество строк. Но я думаю, что эта метрика столь популярна лишь потому, что ее проще всего измерить. Не думаю, что кто-то всерьез считает ее истинной проверкой длины программы. В разных языках существуют разные соглашения о том, сколько всего следует помещать в одну строку; в C во многих строках нет ничего, кроме одного-двух разделителей.

Еще один простой критерий — количество символов в программе, но и он не слишком хорош; в некоторых языках (например, в Perl) просто используются более короткие идентификаторы, чем в других.

Я думаю, что лучшим мерилом размера программы было бы количество элементов, где элемент — это все, что стало бы отдельным узлом, если бы вы нарисовали дерево, представляющее исходный код. Имя переменной или функции — это элемент; целое число или число с плавающей точкой — это элемент; фрагмент литерального текста — это элемент; элемент шаблона или директива форматирования — это элемент; новый блок — это элемент. Бывают пограничные случаи (является ли -5 двумя элементами или одним?), но, думаю, в большинстве языков они трактуются одинаково, поэтому не сильно влияют на сравнения.

Эту метрику еще нужно развить, и в случае конкретных языков она может потребовать интерпретации, но, полагаю, она пытается измерить именно то, что нужно, — количество частей, из которых состоит программа. Мне кажется, что дерево, которое вы нарисовали бы в ходе этого упражнения, — это то самое, что вам приходится выстраивать в голове, чтобы осмыслить программу, и поэтому его размер пропорционален объему работы, необходимой для ее написания или чтения.

Дизайн

Такая метрика позволила бы нам сравнивать разные языки, но в этом, по крайней мере для меня, заключается не главная ее ценность. Главная ценность проверки на краткость — служить ориентиром при проектировании языков. Самое полезное сравнение языков — это сравнение двух потенциальных вариантов одного и того же языка. Что я могу сделать в языке, чтобы сделать программы короче?

Если концептуальная нагрузка программы пропорциональна ее сложности, а отдельный программист способен выдерживать лишь фиксированную концептуальную нагрузку, то это то же самое, что спросить: что я могу сделать, чтобы позволить программистам успевать как можно больше? А это, на мой взгляд, равносильно вопросу: как спроектировать хороший язык?

(К слову, ничто не делает столь очевидной ложность старого заблуждения «все языки эквивалентны», как проектирование языков. Разрабатывая новый язык, вы постоянно сравниваете два языка — язык, если бы я сделал X, и язык, если бы я этого не сделал, — чтобы решить, какой из них лучше. Если бы этот вопрос действительно не имел смысла, с тем же успехом можно было бы подбрасывать монетку.)

Стремление к краткости кажется отличным способом поиска новых идей. Если вам удается сделать нечто такое, что делает короче множество совершенно разных программ, это, скорее всего, не совпадение: вероятно, вы открыли полезную новую абстракцию. Вы могли бы даже написать программу, которая помогала бы в этом, занимаясь поиском повторяющихся паттернов в исходном коде. Среди прочих языков идеи стоит искать в тех, что славятся своей краткостью: Forth, Joy, Icon.

Сравнение

Первым, кто написал об этих вопросах, насколько мне известно, был Фред Брукс в «Мифическом человеко-месяце». Он писал, что программисты выдают примерно одинаковое количество кода в день независимо от языка. Когда я впервые прочитал это, когда мне было чуть за двадцать, это стало для меня огромной неожиданностью и, казалось, влекло за собой колоссальные последствия. Это означало, что (а) единственный способ быстрее писать программное обеспечение — использовать более краткий язык, и (б) тот, кто возьмет на себя труд поступить именно так, сможет оставить конкурентов далеко позади.

Гипотеза Брукса, если она верна, кажется самой сутью хакерства. В последующие годы я обращал пристальное внимание на любые свидетельства по этому вопросу, от официальных исследований до историй об отдельных проектах. Я не нашел ничего, что противоречило бы его словам.

Я пока не видел доказательств, которые показались бы мне исчерпывающими, да и не рассчитываю их увидеть. Исследования вроде сравнения языков программирования Лутца Прехельта, хоть и дают ожидаемые мной результаты, как правило, используют задачи, которые слишком коротки, чтобы служить значимыми тестами. Более надежная проверка языка — это то, что происходит в программах, на написание которых уходит месяц. И единственное настоящее испытание (если вы, как и я, верите, что главное предназначение языка — быть удобным инструментом для размышлений, а не просто способом передать компьютеру указания после того, как вы все уже придумали) — это то, какие принципиально новые вещи вы можете на нем написать. Поэтому любое сравнение языков, где нужно уложиться в заранее заданную спецификацию, проверяет немного не то.

Настоящая проверка языка заключается в том, насколько хорошо с его помощью удается находить и решать новые проблемы, а не в том, насколько хорошо вы можете применить его для решения задачи, которую уже сформулировал кто-то другой. Это два совершенно разных критерия. В искусстве такие техники, как вышивание или мозаика, прекрасно работают, если вы заранее знаете, что хотите получить, но совершенно непригодны, если вы этого еще не знаете. Когда вы хотите нащупать образ прямо в процессе работы — как это неизбежно происходит с любым сложным объектом, например, с портретом человека, — вам нужен более гибкий материал, вроде карандаша, туши или масляных красок. И действительно, гобелены и мозаики на практике создаются так: сначала пишется картина, а затем копируется. (Слово «картон» изначально использовалось для обозначения эскиза, предназначенного для этой цели).

Это означает, что мы вряд ли когда-либо получим достоверные сравнения относительной силы языков программирования. У нас будут точные сравнения, но не достоверные. В частности, целенаправленные исследования по сравнению языков, поскольку в них наверняка будут использоваться небольшие задачи и обязательно задачи с заранее заданными условиями, будут склонны недооценивать возможности более мощных языков.

Отчеты с полей, хоть они неизбежно и будут менее строгими, чем «научные» исследования, скорее всего, окажутся более содержательными. Например, Ульф Вигер из Ericsson провел исследование, в котором пришел к выводу, что Erlang в 4–10 раз лаконичнее C++ и позволяет пропорционально быстрее разрабатывать программное обеспечение:

Сравнения проектов разработки внутри Ericsson указывают на схожую производительность труда в пересчете строк в час, включая все фазы разработки ПО, практически независимо от того, какой язык (Erlang, PLEX, C, C++ или Java) использовался. Различием между разными языками в таком случае становится объем исходного кода.

В исследовании также прямо рассматривается мысль, которая у Брукса присутствовала лишь неявно (поскольку он измерял строки отлаженного кода): программы, написанные на более мощных языках, как правило, содержат меньше ошибок. В таких сферах применения, как сетевые коммутаторы, это само по себе становится самоцелью, возможно, даже более важной, чем продуктивность программистов.

Проверка вкусом

В конечном счете, я думаю, приходится доверять собственной интуиции. Каково это — программировать на этом языке? Мне кажется, способ найти (или спроектировать) лучший язык — это развить в себе сверхчувствительность к тому, насколько хорошо язык позволяет вам мыслить, а затем выбрать или создать тот язык, который ощущается лучше всего. Если какая-то особенность языка кажется неудобной или сковывающей, не переживайте — вы это сразу почувствуете.

Такая сверхчувствительность обойдется недешево. Вы обнаружите, что просто не можете терпеть программирование на неуклюжих языках. Для меня программировать на языках без макросов невыносимо тесно — точно так же, как человеку, привыкшему к динамической типизации, невыносимо возвращаться к программированию на языке, где нужно объявлять тип каждой переменной и нельзя создать список объектов разных типов.

И я не одинок. Я знаю многих Lisp-хакеров, с которыми произошло то же самое. На самом деле наиболее точной мерой относительной силы языков программирования мог бы стать процент людей, знающих язык и готовых взяться за любую работу, где этот язык используется, независимо от предметной области.

Скованность

Думаю, большинство хакеров знают, каково это, когда язык кажется сковывающим. Что происходит, когда вы это чувствуете? По-моему, это то же ощущение, когда улица, по которой вы хотели поехать, перекрыта, и вам приходится делать огромный крюк, чтобы попасть в нужное место. Есть мысль, которую вы хотите выразить, но язык вам этого не позволяет.

Что здесь происходит на самом деле, так это, по-моему, то, что сковывающий язык — это язык недостаточно краткий. Проблема не просто в том, что вы не можете выразить задуманное так, как планировали. Проблема в том, что обходной путь, который навязывает вам язык, длиннее. Проведите мысленный эксперимент. Представьте, что есть программа, которую вы хотите написать, и язык не позволяет вам выразить ее задуманным образом, но взамен вынуждает написать ее другим способом, который оказывается короче. Для меня, по крайней мере, это не ощущалось бы как ограничение. Это все равно как если бы нужную вам улицу перекрыли, а регулировщик на перекрестке направил бы вас не в объезд, а по срезке. Здорово же!

Думаю, большая часть (процентов девяносто?) ощущения скованности возникает от необходимости делать программу, которую вы пишете на языке, длиннее той, что сложилась у вас в голове. Ограниченность — это по большей части нехватка краткости. Поэтому, когда язык ощущается сковывающим, это (в основном) означает, что он недостаточно краток; а если язык не краток, он непременно будет казаться сковывающим.

Читаемость

В цитате, с которой я начал, упоминаются еще два свойства: регулярность и читаемость. Я не совсем понимаю, что такое регулярность или какое преимущество (если оно вообще есть) код, являющийся регулярным и читаемым, имеет перед кодом, который просто читаем. Но, думаю, я понимаю, что подразумевается под читаемостью, и, по-моему, она тоже связана с краткостью.

Здесь нужно быть внимательными и различать читаемость отдельной строки кода и читаемость всей программы целиком. Значение имеет именно второе. Я согласен, что строка на Basic, вероятно, будет более читаемой, чем строка на Lisp. Но в программе, написанной на Basic, будет куда больше строк, чем в той же программе, написанной на Lisp (особенно когда вы переступите границу страны Гринспена). Совокупные усилия на чтение программы на Basic наверняка окажутся больше.

совокупные усилия = усилия на строку x количество строк

Я не столь уверен в том, что читаемость прямо пропорциональна краткости, как в случае с силой, но краткость определенно выступает множителем (в математическом смысле; см. формулу выше) читаемости. Поэтому фраза о том, что целью языка является читаемость, а не краткость, возможно, даже лишена смысла; это все равно что сказать: целью была читаемость, а не читаемость.

Читаемость отдельной строки означает для пользователя, впервые сталкивающегося с языком, лишь то, что исходный код будет выглядеть безобидно. Таким образом, читаемость на уровне строки может быть удачным маркетинговым решением, даже если это плохое инженерное решение. Это напоминает весьма успешный прием с продажей товаров в рассрочку: вместо того чтобы пугать покупателя высокой стоимостью сразу, ему называют скромный ежемесячный платеж. Однако покупка в рассрочку в конечном итоге невыгодна покупателю, точно так же как простая читаемость отдельной строки, вероятно, невыгодна программисту. Покупателю предстоит сделать очень много этих скромных, крошечных платежей; а программисту придется прочитать очень много этих легко читаемых по отдельности строк.

Этот компромисс возник задолго до появления языков программирования. Если вы привыкли читать романы и газетные статьи, первый опыт чтения математической статьи может повергнуть в уныние. На чтение одной страницы может уйти полчаса. И все же я совершенно уверен, что дело не в нотации, даже если кажется обратное. Математическую статью трудно читать потому, что сложны сами идеи. Если бы те же идеи излагались обычным текстом (как математикам и приходилось делать до изобретения лаконичных обозначений), читать статью легче не стало бы, ведь она разрослась бы до размеров книги.

До какой степени?

Немало людей отвергает идею о том, что краткость = сила. Мне кажется, вместо того чтобы просто спорить, равны они или нет, было бы полезнее спросить: до какой степени краткость = сила? Ведь очевидно, что краткость — это значительная часть того, ради чего существуют языки более высокого уровня. Если это не единственное их назначение, то для чего еще они служат и насколько важны эти другие функции по сравнению с краткостью?

Я предлагаю это не просто ради того, чтобы сделать дискуссию более цивилизованной. Мне действительно хочется узнать ответ. Бывает ли язык — и если да, то когда, — чересчур кратким себе во вред?

Гипотеза, с которой я начал, заключалась в том, что, за исключением патологических случаев, краткость можно считать эквивалентной силе. Я имел в виду, что в любом осмысленно спроектированном языке они будут идентичны, хотя, если бы кто-то задался целью разработать язык специально для опровержения этой гипотезы, ему, вероятно, это удалось бы. Впрочем, я даже в этом не уверен.

Языки, а не программы

Следует четко понимать, что речь идет о краткости языков, а не отдельных программ. Отдельные программы, безусловно, могут быть написаны слишком плотно.

Я писал об этом в книге On Lisp. Сложный макрос порой должен экономить во много раз больше строк, чем занимает сам, чтобы быть оправданным. Если написание какого-то заковыристого макроса позволяет экономить десять строк кода при каждом вызове, а сам макрос занимает десять строк, то вы выигрываете в количестве строк уже при повторном его использовании. Но это все равно может оказаться неудачным решением, поскольку определения макросов читать сложнее, чем обычный код. Возможно, вам придется применить этот макрос десять или двадцать раз, прежде чем он принесет реальный выигрыш в общей читаемости.

Уверен, подобные компромиссы существуют в каждом языке (хотя подозреваю, что с ростом мощности языка ставки возрастают). Каждый программист наверняка сталкивался с кодом, который какой-нибудь умник сделал чуть-чуть короче за счет сомнительных трюков.

Так что по этому поводу споров нет — по крайней мере, с моей стороны. Отдельные программы определенно могут быть слишком краткими себе во вред. Вопрос в том, может ли быть таким сам язык? Может ли язык вынуждать программистов писать код, который короток (в элементах) в ущерб общей читаемости?

Одна из причин, почему трудно представить себе слишком краткий язык, заключается в том, что если существует некий чрезмерно компактный способ что-то выразить, то, вероятно, найдется и более длинный. Например, если вам кажется, что программы на Lisp с обилием макросов или функций высшего порядка слишком перегружены, вы при желании можете писать код, изоморфный языку Pascal. Если вы не хотите выражать факториал в Arc как вызов функции высшего порядка (rec zero 1 * 1-), вы всегда можете написать рекурсивное определение: (rfn fact (x) (if (zero x) 1 (* x (fact (1- x))))). И хотя с ходу я не могу припомнить подходящих примеров, мне интересен вопрос: может ли язык быть слишком кратким? Существуют ли языки, заставляющие писать код коряво и непонятно? Если у кого-то есть примеры, мне было бы чрезвычайно любопытно на них взглянуть.

(Напоминание: меня интересуют программы, которые очень плотны по метрике «элементов», описанной выше, а не просто программы, которые коротки из-за возможности опускать разделители и давать всему односимвольные имена.)