[Перевод] Если изобрести язык программирования 21 века
Автор материала рассуждает о проблемах современных языков программирования и о том, какими путями можно исправить недостатки.
Только за последние 18 лет люди придумали множество языков, среди которых, вероятно, самыми популярными стали Swift, Kotlin и Go. При этом отличительная черта языка программирования 21 века — это отсутствие каких-либо отличительных черт. Самое приятное в работе с такими языками — за изучением одного из них можно провести выходные и под конец заявить, что вам удалось освоить популярную новинку, по факту же не узнав ничего нового. В них действительно нет ничего нового. Все современные языки созданы на основе какой-либо правильной и проверенной формулы, имя которой, вероятнее всего, Objective-C, Java или C.
«Отсутствие новизны» можно считать ценной чертой, но подобная ситуация вызывает один вопрос. Действительно ли перед нами языки нового, 21 века, или все это — просто отражение плохих привычек программирования 20 века?
Если бы я изобретал язык, я бы не старался исправить прошлое, а попытался бы создать нечто, что хорошо работало бы именно в условиях современности, но также было способно развиваться и выдерживать проверку временем. Если для этого требуются радикальные конструктивные решения, то так тому и быть.
Долой синтаксис!
Синтаксис современных языков отражает попытку втиснуть свободу мела и доски в оковы ASCII. Некоторые элементы записи, такие как арифметические знаки или скобки, воспринимаются более-менее естественно. Но ряд других обозначений оправдан разве что экономией усилий при нажатии кнопок телетайпа.
Ввод текста с клавиатуры уже не представляет сложностей. Мы не обязаны ставить себя в положение, когда необходимо угадывать значение синтаксиса. Штуки вроде (($:@(<#[), (=#[), $:@(>#[)) ({~ ?@#)) ^: (1<#) — очень краткий и емкий формат записи (это, к слову, настоящий фрагмент кода в реальном языке), но он никаким образом не улучшает читабельность. И, что важнее, его сложно «прогуглить» или найти на stackoverflow.
То же самое можно сказать и про таинственные названия функций, условные обозначения кодов возврата и атрибутов с малопонятным значением. Они хорошо послужили в прошлом, сэкономив немало места на перфокартах, но сегодня им пора на заслуженный покой.
Нечто вроде
FILE * test_file = fopen("/tmp/test.txt", "w+");
должно преобразиться в
create file /tmp/test.txt for input and output as test_file
Нам не нужны все эти скобки, кавычки, звездочки и точки с запятыми (если, конечно, они действительно не доносят идею понятнее). Подсветка синтаксиса вполне способна полностью заменить синтаксические обозначения.
Некоторые вещи в избытке доступны в 21 веке: например, скорость синтаксического анализа, компьютерная память, онлайн-поиск. Другие ресурсы по-прежнему в цене: время разработки, память программиста, усилия, потраченные на изучение особенностей языка. Изменения в правилах написания кода должны сместить акцент в пользу более дешевых ресурсов и экономии более дорогих.
Долой встроенные типы!
Вы наверняка знакомы с парадоксами JavaScript. Например, такими:
> 10.8 / 100
0.10800000000000001
Этот результат характерен не только для JavaScript. И это вовсе не парадокс, а образец абсолютно корректного следования всеми уважаемому стандарту IEEE 754. Подобная реализация чисел с плавающей запятой встречается практически во всех архитектурах. И она не так плоха, учитывая, что мы пытаемся втиснуть бесконечное множество вещественных чисел в 32, 64 или 256 бит.
То, что математики считают невозможным, инженеры воплощают через отказ от здравого смысла в угоду практической реализации. Числа с плавающей запятой в интерпретации IEEE — вообще не числа. Математика требует ассоциативности от операции их сложения. Типы float и double не всегда сохраняют это свойство. Математика требует, чтобы множество вещественных чисел включало в себя целые числа, но это требование не выполняется даже для float и uint32_t одного размера. Математика требует, чтобы у вещественных чисел был нулевой элемент. Что ж, в этом отношении стандарт IEEE превосходит все ожидания, ведь у чисел с плавающей запятой есть два нулевых элемента вместо одного.
Похожие особенности есть не только у чисел с плавающей запятой. Встроенные целые числа реализованы не лучше. Знаете ли вы, что произойдет, если попытаться сложить два таких 16-разрядных числа?
0xFFFF + 0x0001
Точного ответа не даст никто. Чутье подсказывает, что переполнение даст 0×0000. Однако такой исход не задокументирован ни в одном мировом стандарте. В обработке этой операции все ориентируются на подход C и семейства процессоров x86. В качестве альтернативных вариантов может получиться 0xFFFF или будет вызвано прерывание, или некий специальный, указывающий на переполнение бит будет сохранен в особом месте.
Такие моменты вообще нигде не рассматриваются, и правила обработки подобных операций отличается от языка к языку. Если странности плавающей запятой хотя бы закреплены стандартом, то последний поднятый вопрос в принципе непредсказуем.
Вместо этого для численных расчетов я бы предложил ввести типы данных определяемой величины с фиксированной запятой и со стандартизированным поведением при потере точности или выходах за верхнюю или нижнюю границу. Нечто вроде этого:
1.000 / 3.000 = 0.333
0001 + 9999 = overflowed 9999
0.001 / 2 = underflowed 0
Необязательно дописывать все конечные нули: их наличие должно подразумеваться определением типа данных. Но важно иметь возможность выбора максимальных и минимальных границ самостоятельно, и не зависеть от архитектуры процессора.
Разве такие вычисления не будут работать медленнее? Да, будут. Но спросите себя: как часто вам приходится программировать высокопроизводительные вычисления? Полагаю, что если вы не специалист в этой области, то очень редко. А если вы и занимаетесь подобными задачами, то пользуетесь для этих целей специализированным оборудованием и компиляторами. Насколько я могу судить, типичный программист 21 века нечасто решает дифференциальные уравнения.
Как бы то ни было, ничто не мешает использовать быстрые, сложные и непредсказуемые встроенные типы из прошлого в качестве альтернативы, а не как вариант по умолчанию.
Долой практику метаязыков!
Существуют замечательные языки, придуманные не для выполнения задач, а для создания языков, которые способны их выполнять. Racket, Rebol и Forth — лишь несколько примеров. Они все мне нравятся, играть с ними — чистое удовольствие. Но, как вы наверное догадались, удовольствие, получаемое от работы с языком, — не главный критерий, делающий язык универсальным и популярным.
Возможность создавать новые языки внутри языка для выполнения той или иной задачи — очень мощное средство, которое сполна окупается во время проведения самостоятельных исследовательских работ. К сожалению, если код должен быть понятен не только автору, то помимо основного придется обучать других людей и новому внутреннему языку. И вот тут начинаются проблемы.
Люди хотят выполнить поставленную задачу, а не выучить язык, который поможет сделать работу ровно один раз, а после нигде не пригодится. Для посторонних людей идея освоить ваш язык — это вложение, которое едва ли окупится. А вот изучение чего-то стандартизированного — это инвестиция на всю жизнь. Поэтому скорее они заново перепишут ваш код и уже потом выучат его. Так на свет и появляются бесчисленные диалекты для одной прикладной сферы. Люди спорят об эстетике, идеологии, архитектуре и прочих маловажных вещах. А миллионы строк кода пишутся, чтобы через несколько месяцев кануть в небытие.
Ребята, пишущие на Lisp, прошли через это в 80-х. Они поняли, что чем больше прикладных элементов языка будут стандартизированы, тем лучше. Так на свет появился Common Lisp.
И он оказался огромен. Стандарт INCITS 226–1994 насчитывает 1153 страницы. Этот рекорд 17 лет спустя побил только C++ со стандартом ISO/IEC 14882:2011 (1338 страниц). С++ приходится тащить за собой неподъемный багаж наследия, хотя он не всегда был таким большим. Common Lisp был создан по большей части с нуля.
Языку программирования не следует быть настолько огромным. В этом нет необходимости. Ему просто нужна хорошая стандартная библиотека, заполненная всевозможными полезными штуками, чтобы людям не пришлось изобретать велосипеды.
Конечно, поддерживать баланс между размерами и прикладной пригодностью непросто. Опыт C++ на практике показал как это трудно. Полагаю, чтобы достичь нужного равновесия, язык 21 века должен условно затачиваться под определенную прикладную область. Поскольку больше всего проблем сейчас возникает именно в области бизнес-приложений, языку, вероятно, следует ориентироваться на проблемы бизнеса, а не на разработку игр или веб-дизайн.
Итак…
Язык 21 века должен быть бизнес-ориентированным, использовать понятные языковые выражения и не зависеть от встроенных типов. Здорово, что такой язык уже существует! Как думаете, о чем идет речь?
Да, это COBOL.
Это один из первых высокоуровневых языков, сегодня по большей части забытый. Вынужден признаться, что я намеренно описал древние фичи COBOL«a как ультрасовременные и невероятно многообещающие. И сделал я это, чтобы показать одну вещь. Код пишут не языковые фичи. Это делаете вы.
Наивно думать, что язык отвечает за качество кода, и что добавление некоторых прибамбасов (или их удаление) может автоматически все улучшить. В свое время программисты не взлюбили Fortran и COBOL, потому изобрели C++ и Java, чтобы в итоге прийти к ситуации, когда 20 или 30 лет спустя они тоже всем разонравились.
По моим ощущениям, корень проблемы кроется где-то в области социологии и психологии, но не программирования. Действительно ли языки нам настолько не нравятся? И разве мы довольны окружением, в котором работаем? Windows уязвим, Visual Studio работает слишком медленно, из Vim«а невозможно выйти. На самом деле именно эти вещи вызывают недовольство, а не творческий процесс сам по себе.
Но ведь всегда надо найти виноватого. Будучи инженерами ПО, частично несущими ответственность за то, какими паршивыми бывают программы, мы не станем обвинять себя, верно? Поэтому ищем изъяны в инструментах. Давайте изобретать новые COBOL«ы до тех пор, пока в один прекрасный день солнце не начнет светить ярче, птицы не запоют громче, а Windows не начнет загружаться за 2 секунды.
Но, скорее всего, этот день никогда не настанет.
Поэтому, если бы я захотел изобрести язык программирования 21 века, вместо этого я попытался бы найти новый подход к ответственности. Или новый способ лучшего освоения имеющихся инструментов. Я бы попытался внимательнее относится к существенным деталям и беспощадно избавляться от любой излишней сложности. Вместо языков, входящих и выходящих из моды, всегда есть некие фундаментальные вещи, заслуживающие постоянного переосмысления.