Succinctness is Power

Оригинал Перевод

Май 2002

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

- Чарльз Бэббидж

В дискуссии вокруг статьи "Месть зануд" в рассылке LL1 Пол Прескод высказал мысль, которая не выходит у меня из головы.

На первый взгляд, язык программирования скорее не должен претендовать на такое. Насколько я понимаю, краткость = сила. А если так, то делая подстановку, мы получаем:

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

Действительно ли краткость = сила? Похоже это важный вопрос, может самый важный вопрос для тех, кто занимается разработкой языков. Я пока не уверен, что ответ на него - просто "да", но для начала это неплохая гипотеза.

Гипотеза

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

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

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

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

Мера

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

Другой простой тест - это количество символов в программе, но и этот не слишком хорош; у некоторых языков (например Perl) идентификаторы короче, чем у других.

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

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

Проектирование

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

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

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

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

Сравнение

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

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

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

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

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

Отчеты в этой области, хоть и будут менее точными, чем "научные" исследования, скорее всего будут более осмысленными. Например, Ульф Вигер из компании Ericsson провел исследование и пришел к выводу, что Erlang в 4-10 раз более краток, чем Си++, а скорость разработки ПО на нем пропорционально выше:

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

Вкусы

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

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

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

Ограничения

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

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

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

Читабельность

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

Здесь мы должны быть осторожны с понятиями читабельности отдельной строки кода и читабельности программы в целом. Важно только последнее. Соглашусь, что одна строка на Бейсике скорее всего более читабельна, чем одна строка на Лиспе, но программа написанная на Бейсике будет иметь больше строк, чем та же программа написанная на Лиспе. На прочтение Бейсик-программы будет потрачено больше усилий.

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

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

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

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

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

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

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

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

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

Я писал об этом в книге "О Лиспе". Для того, чтобы макрос оправдался, он должен сэкономить во много раз больше места по отношению к собственной длине. Если какой-нибудь громоздкий макрос экономит десять строк кода всякий раз, когда вы его используете, и сам макрос состоит из десяти строк, то вы получите экономию в строках в случае, если используете его более двух раз. Но это по-прежнему плохой ход, поскольку читать макро-определения сложнее, чем обычный код. Возможно вам придется использовать макрос 10 или 20 раз прежде, чем улучшится читабельность.

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

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

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

то вы можете также записать рекурсивное определение:

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

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