http://habrahabr.ru/company/mailru/blog/271585/
Сегодня многие системы и языки программирования позиционируются как «мощные». Нельзя сказать, что это плохо. Почти каждый из нас считает это положительным свойством. Но в этом посте я хочу донести такую точку зрения, что во многих случаях нам нужны
менее мощные языки программирования и системы. Но прежде чем продолжить, уточню: здесь будет мало оригинальных, моих собственных размышлений. Я буду излагать ход мыслей, возникший по прочтении книги Дугласа Хофштадтера «
Гёдель, Эшер, Бах», которая помогла мне собрать воедино разрозненные идеи и мысли, бродившие в голове. Также большое влияние на нижеизложенный материал оказали
пост Филипа Вадлера и
видеозапись с конференции Scala. Ключевая мысль такова:
Каждое увеличение выразительности возлагает дополнительную нагрузку на всех, кто хочет понять сообщение.
И я хочу лишь проиллюстрировать эту точку зрения с помощью примеров, которые будут ближе и понятнее сообществу программистов на Python.
Несколько слов относительно определений. Что подразумевается под более или менее мощными языками программирования? В рамках поста это можно приблизительно охарактеризовать так: «свобода и возможность делать все, что хочется» с точки зрения человека, пишущего код или вводящего данные в систему. Это примерно коррелирует с идеей «выразительности», хотя и не является формальным определением. Если говорить еще точнее, то многие языки обладают одинаковым уровнем выразительности с точки зрения
полноты по Тьюрингу. Но мы, разработчики, все же выделяем какие-то из них как более мощные, поскольку они позволяют нам получать определенный результат посредством меньшего количества кода, либо несколькими разными способами, что дает больше свободы.
Но с этой свободой не все так просто. Каждый бит «мощи», востребованной вами при использовании какого-либо языка, равен отдаче, которую вы должны обеспечить, когда кто-то «потребляет» написанное вами. Ниже приведу примеры, формально относящиеся к программированию, но в то же время находящие отклик в душе.
Кто-то спросит: «А имеет ли это вообще какое-то значение?» Конечно, имеет — в той степени, чтобы можно было «потреблять» результат работы вашей системы. В качестве «потребителей» могут выступать специалисты по поддержке ПО, компиляторы и прочие инструменты для разработчиков, поэтому вы почти всегда заботитесь не только о производительности и корректности работы ваших продуктов, но и о людях.
Базы данных и схемы
На нижнем уровне шкалы «выразительности» располагается то, что можно отнести, скорее, к данным, а не к языкам программирования. Но и данные, и язык нужно воспринимать как «получаемое кем-то сообщение», поэтому здесь применим тот же подход.
За годы работы меня часто просили сделать поля для ввода текста в произвольной форме. С точки зрения конечного пользователя такое поле воплощает наивысший уровень «мощи», ведь в него можно ввести что угодно. И в рамках этой логики подобное поле является «наиболее полезным».
Но именно потому, что в поле можно вводить любой текст, оно является самым бесполезным, поскольку хуже поддается структурированию. Даже поиск по таким полям работает ненадежно — из-за возможных опечаток и разных способов описания каких-то вещей. Чем дольше я работаю с базами данных, тем сильнее желание жестко регламентировать все, что можно. И когда мне удается это сделать, то из получаемых данных можно извлечь гораздо больше пользы. То есть чем больше я ограничиваю «мощь» (то есть свободу) источников, вводящих данные в систему, тем больше возможностей я получаю при «потреблении» этих данных.
То же самое можно сказать относительно технологий баз данных. Неструктурированные (schemaless) БД предоставляют широкие возможности и гибкость при вводе данных, и бесполезнее при их выводе. Key-value хранилище представляет собой аналог «текста в свободной форме» с теми же недостатками: от него мало толку, если вы хотите извлечь информацию или что-то сделать с данными, поскольку не можете быть уверены в том, что в хранилище присутствуют какие-то конкретные ключи.
HTML
Отчасти, успех веба был обусловлен намеренным ограничением возможностей ключевых технологий — HTML и CSS. Да, это языки разметки, а не программирования, но такими их сделали не случайно. Это была
сознательно выбранная концепция, одним из основоположников которой стал Тим Бернерс Ли. Просто процитирую один отрывок:
«С 1960-х по 1980-е информатика прилагала немало усилий по созданию все более мощных языков программирования. Но сегодня есть причины, по которым нужно выбирать менее мощные инструменты. Одной из них является то, что чем «слабее» язык, тем больше вы можете сделать с хранящимися в нем данными. Если вы напишете простую декларативную форму, то кто угодно сможет написать программу, анализирующую эту форму самым разными способами. В некоем общем смысле, Семантическая Сеть представляет собой попытку преобразования больших объемов данных в обычный язык, чтобы получить такие возможности по анализу этих данных, которые и не снились их создателям.
К примеру, если веб-страница использует RDF для представления прогноза погоды, то пользователи смогут извлечь эти данные в виде таблицы, как-то обработать, усреднить, построить графики, сопоставить с другой информацией. И сравните это с Java-апплетом: информация может быть подана очень красиво, но ее невозможно анализировать. Поисковый бот не поймет, что представлено на странице. Единственный способ узнать, что же делает Java-апплет, это запустить его перед сидящим перед экраном человеком».
Такую же позицию занял и
консорциум W3C:
«Хорошая практика: использовать наименее мощный язык, пригодный для выражения информации, связей или приложений во Всемирной Сети».
Это практически полностью противоречит совету
Пола Грэма (с оговоркой, что зачастую сравниваются определения «мощи», далекие от формальных):
«Если вы можете выбирать из нескольких языков, то при прочих равных было бы ошибкой программировать не на наиболее мощном из них».
Файл формата MANIFEST.in
Перейдем теперь к «настоящим» языкам программирования. В качестве примера я выбрал формат файла MANIFEST.in, используемого инструментами distutils и setuptools. Если вам доводилось создавать пакеты для Python-библиотек, то вы наверняка знакомы с этим форматом.
По сути, он представляет собой очень маленький язык, описывающий, какие файлы должны входить в состав пакета в Python (по отношению к файлу MANIFEST.in, вызываемого из рабочего каталога). Например:
include README.rst
recursive-include foo *.py
recursive-include tests *
global-exclude *~
global-exclude *.pyc
prune .DS_Store
Существует два типа директив:
- include (include, recursive-include, global-include и graft)
- exclude (exclude, recursive-exclude, global-exclude и prune)
Возникает вопрос: как интерпретируются эти директивы? То есть какова их семантика?
Можно интерпретировать так: «
Файл из рабочего каталога (или подкаталога) должен быть включен в пакет, если соответствует хотя бы одной директиве типа include и не соответствует ни одной директива типа exclude».
Вроде бы, это говорит о том, что данный язык является декларативным. К сожалению, это не так. В
документации distutils говорится про MANIFEST.in — директивы нужно понимать следующим образом:
- начните с пустого списка файлов, которые должны быть включены в пакет. Точнее, начните со списка по умолчанию;
- выполняйте директивы в MANIFEST.in согласно их очередности;
- для каждой директивы типа include скопируйте все соответствующие файлы из рабочего каталога в список пакета;
- для каждой директивы типа exclude удалите все соответствующие файлы из списка пакета.
Этот пример наглядно иллюстрирует императивную природу этого языка: каждая строка MANIFEST.in является командой, чье действие подразумевает наличие побочных эффектов. Это делает язык
более мощным, чем псевдодекларативный вариант, приведенный выше. Рассмотрим пример:
recursive-include foo *
recursive-exclude foo/bar *
recursive-include foo *.png
В результате выполнения списка этих команд png-файл (ниже foo/bar) оказывается включенным в пакет. А все, что выше foo/bar, в пакет не включается. Добиться того же результата средствами декларативного языка было бы сложнее, например:
recursive-include foo *
recursive-exclude foo/bar *.txt *.rst *.gif *.jpeg *.py ...
Поскольку императивный язык мощнее, существует соблазн выбрать именно его. Однако у императивной версии есть ряд важных недостатков:
- Трудности с оптимизацией. Когда дело доходит до интерпретирования MANIFEST.in и формирования списка файлов для включения в пакет, есть лишь одно эффективное решение: сначала сделать неизменяемый список всех файлов в каталоге и подкаталогах, а затем применять к нему правила. Cкопировать файлы в выходной список согласно правилам добавления, а затем удалить из него какие-то файлы согласно правилам исключения. Такой подход сейчас реализован в Python.
Вполне рабочий вариант, если у вас относительно немного файлов. А когда список состоит из тысяч позиций, большинство из которых должны быть исключены из пакета, то на формирование окончательного списка уходит много времени.
Решение этой проблемы выглядит очевидным: не лезть в каталоги, которые будут исключены какими-то директивами. Но это возможно только в том случае, если директивы исключения следуют после всех директив включения.
Данная проблема не является теоретической. Я обнаружил, что из-за большого количества файлов в рабочем каталоге, если использовать, например, инструмент tox, выполнение setup.py sdist и ряда других команд может достигать 10 минут. То есть сам tox (использующий setup.py) будет работать очень медленно. Сейчас я пытаюсь решить эту проблему, но, думаю, сделать это будет очень непросто.
На первый взгляд, можно было бы сделать оптимизацию (менее интенсивно использовать файловую систему за счет выполнения исключающих директив после всех добавляющих), но это заметно все усложняет, и патч вряд ли будет принят. Увеличится количество ветвей кода, а значит, возрастет вероятность возникновения многочисленных ошибок.
Вероятно, единственным приемлемым решением будет вообще отказ от использования MANIFEST.in, и оптимизировать только в тех случаях, когда он совершенно пустой.
- Обратная сторона «мощи»: в файлах MANIFEST.in труднее разобраться. В первую очередь, сложнее освоить принципы работы языка. Для декоративной версии документация была бы значительно короче, чем есть на самом деле.
Кроме того, при анализе специфических MANIFEST.in приходится мысленно исполнять команды, пытаясь представить результат. Куда проще было бы размещать строки в том порядке, в каком было бы удобно вам.
Все это приводит к возникновению ошибок при создании пакетов. Например, легко поверить, что директива global-exclude *~ в начале MANIFEST.in означает, что все файлы, чьи названия оканчиваются на ~ (временные файлы некоторых редакторов), будут исключены из пакета. На самом деле эта директива вообще ничего не делает. И если одна из последующих директив попытается включить какие-то файлы в пакет, то они будут ошибочно включены. Я нашел следующие примеры этой ошибки (exclude-директивы, не работающие, как задумано):
- hgview (если разместить в самом начале файла, работать не будет);
- django-mailer (неработающая глобальная исключающая директива в начале файла).
- Вы не можете группировать строки в MANIFEST.in для облегчения восприятия, поскольку изменение их очередности влияет на состав пакета.
Роутинг
Роутинг является одной из составляющих ядра Django. Это компонент, который анализирует URL и передает их обработчику данного URL. При этом, возможно, извлекая из URL какие-то компоненты.
В Django это реализовано с помощью регулярных выражений. Допустим, у нас есть приложение, выводящее информацию о котятах, и в файле kittens/urls.py содержится такой код:
from django.conf.urls import url
from kittens import views
urlpatterns = [
url(r'^kittens/$', views.list_kittens, name="kittens_list_kittens"),
url(r'^kittens/(?P<id>\d+)/$', views.show_kitten, name="kittens_show_kitten"),
]
Соответственно, файл views.py выглядит так:
def list_kittens(request):
# ...
def show_kitten(request, id=None):
# ...
Регулярные выражения обладают встроенной функцией захвата, используемой для получения параметров, переданных во view-функции. Пусть наше приложение работает по адресу cuteness.com. Тогда адрес
www.cuteness.com/kittens/23 будет инициировать вызов кода show_kitten(request, id=«23»).
Поскольку мы теперь можем маршрутизировать URL в конкретные функции, веб-приложения вынуждены почти всегда генерировать эти самые URL. Допустим, нам понадобилось включить на страницу со списком котят ссылки на их личные страницы: show_kitten. И наверняка мы захотим сделать это с помощью повторного использования конфигурации URL-маршрутизации.
Однако использовать мы ее будем в обратном направлении. При выполнении URL-маршрутизации выполним следующее:
URL path -> (handler function, arguments)
При генерировании нам известны функция-обработчик и необходимые аргументы. И мы должны сформировать URL, который приведет пользователя на нужную страницу
после выполнения URL-маршрутизации:
(handler function, arguments) -> URL path
Для этого нам необходимо уметь предсказывать поведение механизма маршрутизации. Мы спрашиваем: «Каковы будут входные данные при таких выходных?» В самом начале истории Django в нем еще не было такого функционала. Но
оказалось, что в большинстве случаев можно «изменять направление» URL-шаблона. Регулярные выражения можно парсить с целью поиска статичных и захватываемых элементов.
Обратите внимание, что это возможно лишь потому, что используемый для определения URL-маршрутов язык — регулярные выражения — имеет определенные ограничения. Хотя можно было использовать для этого и гораздо более мощный язык. К примеру, определяя URL с помощью функций, которые:
- используют URL в качестве входных данных;
- при несовпадении выдают NoMatch;
- при совпадении возвращают усеченный URL и набор каких-то захваченных параметров.
Тогда наш urls.py выглядел бы так:
from django.conf.urls import url, NoMatch
def match_kitten(path):
KITTEN = 'kitten/'
if path.startswith(KITTEN):
return path[len(KITTEN):], {}
raise NoMatch()
def capture_id(path):
part = path.split('/')[0]
try:
id = int(part)
except ValueError:
raise NoMatch()
return path[len(part)+1:], {'id': id}
urlpatterns = [
url([match_kitten], views.list_kittens, name='kittens_list_kittens'),
url([match_kitten, capture_id], views.show_kitten, name="kittens_show_kitten"),
]
Конечно, можно было бы сделать match_kitten и capture_id более лаконичными:
from django.conf.urls import url, m, c
urlpatterns = [
url([m('kitten/'), views.list_kittens, name='kittens_list_kittens'),
url([m('kitten/'), c(int)], views.show_kitten, name="kittens_show_kitten"),
]
Учитывая, что m и c являются возвращающими функциями, этот язык получается более мощным для маршрутизации URL, чем реальный, основанный на регулярных выражениях. Интерфейс для обнаружения совпадений и захвата имеет гораздо больше возможностей — например, можно было бы осуществлять поиск ID в базе данных и др.
Но в этой бочке меда присутствует и деготь: мы не смогли бы осуществлять реверсинг URL. В полных по Тьюрингу языках нельзя спросить: «Каковы будут входные данные при таких выходных?» Теоретически можно было бы просмотреть исходный код функции ради поиска известных шаблонов, но это совершенно непрактично.
А с ограниченными по своим возможностям регулярными выражениями получается больше доступных вариантов. В целом, основанная на регулярках конфигурация URL не реверсируется, поскольку простые выражения вроде. (просто точка) нельзя реверсировать уникальным образом. А если мы хотим нормально генерировать классические URL, то нам требуется уникальное решение. Если. все же попадается, то Django произвольно выбирает другой символ, иные подстановочные символы так не обрабатываются. Но поскольку такие символы встречаются только среди захваченных, то вполне можно реверсить регулярные выражения.
Так что если мы хотим надежно реверсить URL-маршруты, то нам будет нужно что-то менее мощное, чем регулярные выражения. В свое время их выбрали только потому, что они были
достаточно мощными, не понимая, что их возможности избыточны.
Помимо прочего, в Python’е не так-то просто определять мини-языки для подобных задач. Их реализация и использование потребуют немалого количества бойлерплейтов и уровня детализации — куда больше, чем при использовании «строковых» языков вроде регулярных выражений. Кстати, в языках типа Haskell подобные вещи делаются намного легче благодаря довольно простым возможностям вроде определения алгебраических типов данных и сопоставления с шаблонами.
Регулярные выражения
Предыдущая глава напомнила мне еще об одной проблеме. В большинстве случаев пользоваться регулярным выражениями довольно просто. Но когда бы вы ни вызвали регулярку, вам сразу становятся доступны все ее возможности, не зависимо от того, нужно вам это или нет. Одним из следствий в некоторых случаях является необходимость backtracking’а, чтобы найти все возможные соответствия. И значит можно умышленно создать такую комбинацию символов, которая будет ОЧЕНЬ долго обрабатываться регулярными выражениями.
Это, кстати, породило
целый класс DoS-уязвимостей, одну из которых обнаружили в Django —
CVE-2015-5145.
Шаблоны: Django vs Jinja
Создатели шаблонизатора
Jinja вдохновлялись
языком шаблонов Django, но несколько изменили его философию и синтаксис.
Производительность является одним из главных преимуществ Jinja2. Здесь сразу компилируется Python-код, вместо того чтобы выполнять написанный на Python интерпретатор, как это сделано в Django, что и дает
5-20-кратное увеличение производительности.
Автор Jinja Армин Ронахер (Armin Ronacher) вполне успешно
применял такой же подход и для ускорения рендеринга шаблонов Django.
Предлагая этот проект, он знал, что API-расширения в Django сильно затрудняют внедрение подхода, реализованного в Jinja. В Django можно использовать собственные шаблонные тэги, что дает
практически полный контроль над этапами компиляции и рендеринга. В том числе можно применять такие мощные тэги, как
addtoblock в django-sekizai, хоть на первый взгляд это и кажется невозможным. Но даже если в подобных случаях (нечастых) использовался бы более медленный вариант, то все равно была бы польза от быстрой реализации.
Было и еще одно важное отличие, влиявшее на многие шаблоны. В Django передаваемый контекстный объект (содержащий необходимые шаблону данные) может перезаписываться в течение процесса рендеринга шаблона. Шаблонные тэги могут назначать контекст, и некоторые из них (например, url) только этим и занимаются.
Все это позволило реализовать в Django
основную часть компиляции в Python по примеру Jinja.
Обратите внимание, что в обоих перечисленных случаях проблема заключается в мощи движка Django — он позволяет авторам кода делать вещи, которые невозможны в Jinja2. В результате мы сталкиваемся с очень большими затруднениями, пытаясь скомпилировать быстрый код.
Это достаточно важный момент, так как в каких-то случаях скорость рендеринга шаблона может стать главной проблемой проекта. Из-за этого
немало продуктов были
переведены на Jinja. И это нездоровая ситуация!
Зачастую лишь задним числом удается понять, что же именно затрудняет оптимизацию. И было бы лукавством утверждать, что введение в язык каких-то ограничений обязательно повлечет за собой облегчение процесса оптимизации. Хотя есть же языки, в которых вполне удачно реализуется концепция ограниченных возможностей для программистов и потребителей!
Можно сказать, что вполне логично было сделать контекстный объект перезаписываемым, поскольку структуры данных в Python по умолчанию мутабельны. Что приводит нас к самому Python…
Python
Можно по-разному относиться к широте возможностей этого языка. Например, насколько это усложняет жизнь каждого разработчика и программы, сталкивающихся с кодом Python.
В первую очередь на ум приходят компиляция и производительность. Практически полное отсутствие ограничений, позволяющее, помимо прочего, перезаписывать классы и модули, не только помогает делать всякие полезные вещи, но и сильно ухудшает производительность. Авторам
PyPy удалось добиться впечатляющих результатов, но,
судя по этой динамике, им вряд ли удастся добиться в будущем существенного прироста. Да и имеющиеся достижения в производительности были достигнуты ценой увеличения потребления памяти. Просто Python-код поддается оптимизации лишь до определенного предела.
На случай, если у вас сложилось такое мнение: я вовсе не являюсь противником Python или Django. Я — один из ведущих разработчиков в Django, и использую его и Python почти во всех моих профессиональных проектах. Этим постом хочу лишь проиллюстрировать проблемы, порождаемые широкими возможностями языков программирования.
Поговорим теперь о рефакторинге и поддержке кода. Если вы создаете серьезные проекты, то наверняка масса времени уходит на поддержку. И очень важно иметь возможность делать это быстро и с минимумом ошибок.
Допустим, в том же Python, при использовании VCS (например, Git или Mercurial), если вы переместите в другое место функцию из десяти строк, то получите diff на 20 строк, несмотря на то, что с точки зрения самой программы ничего не поменялось. А если что-то все же поменялось (функция была не только перемещена, но и модифицирована), это будет очень сложно определить.
Каждый раз, сталкиваясь с этим, возникает недоумение: почему мы работаем с нашим сложноструктурированным кодом как с кучей строк обычного текста? Это же безумие какое-то!
Вероятно, вы считаете, что эту проблему можно решить с помощью
продвинутых diff-инструментов. Но беда в том, что в Python изменение очередности следования функций действительно может повлиять на работу программы (имеются в виду изменения, проявляющиеся во время выполнения).
Вот несколько примеров. Возьмем ранее определенную функцию в качестве аргумента по умолчанию:
def foo():
pass
def bar(a, callback=foo):
pass
Если поменять порядок строк, то для foo в определении bar вылетит ошибка NameError.
Воспользуемся декоратором:
@decorateit
def foo():
pass
@decorateit
def bar():
pass
Из-за возможных эффектов в @decorateit вы не можете поменять очередность этих функций и быть уверенным, что программа будет работать так же. То же можно сказать и о вызове кода в списке аргументов функции:
def foo(x=Something()):
pass
def bar(x=Something()):
pass
Атрибуты класса тоже нельзя менять местами:
class Foo():
a = Bar()
b = Bar()
Здесь определение b поставить выше a из-за возможных эффектов в конструкторе Bar. Это может показаться теоретизированием, но тот же Django действительно использует это внутри определений Model и Form ради обеспечения стандартного порядка полей, применяя хитрый счетчик на уровне класса внутри базового конструктора Field.
Вам придется смириться с тем, что в Python последовательность выражений функции является последовательностью действий, в результате которых создаются объекты (функции и аргументы), с ними осуществляются какие-то действия и т.д. В отличие от некоторых других языков, менять порядок декларирований нельзя.
Это предоставляет вам широчайшие возможности при написании кода, но зато не позволяет развернуться с точки зрения автоматизации манипуляций с уже готовым кодом. Рефакторинг практически невозможно осуществлять без опаски, поскольку из-за возможностей языка (например, «
утиной типизации») нельзя переименовывать методы. А в связи с вероятностью «отражений» и динамического доступа к атрибутам (getattr и прочие) вы вообще не можете безопасно осуществлять переименование в автоматическом режиме.
Так что не спешите обвинять во всех бедах VCS или инструменты для рефакторинга. Все дело в широте возможностей самого Python. Несмотря на огромное количество структур в коде, с ними мало что можно сделать с точки зрения каких-то манипуляций. Так что при ближайшем рассмотрении зависимость diff от порядка строк выглядит уже не так плохо.
Сегодня мы почти не используем декораторы, из-за которых становится важна очередность определений в коде, и потребителям это несколько облегчает жизнь. Но в редких случаях наши инструменты все же оказываются практически бесполезны. Для каких-то потребителей можно провести оптимизацию с учетом некой стандартной ситуации и обнаружить факт сбоя, например, использовать проверки JIT. Но для других средств (скажем, для VCS или инструментов рефакторинга) в случае неудачи будет слишком поздно собирать информацию о выполнении. К моменту обнаружения проблемы вы могли уже зарелизить «испорченный» код, так что лучше проявлять бдительность, чем потом извиняться.
В идеальном языке при переименовании функции diff в VCS должен выглядеть как «Функция foo переименована в bar». Одновременно должна быть предусмотрена возможность экспортирования и импортирования — чтобы можно было обновлять зависимости до версии, в которой foo переименовали в bar. Это можно сделать в «менее мощном» языке. А вот из-за широты возможностей самого Python все остальные инструменты в его окружении могут приносить гораздо меньше пользы.
Насколько это важно, зависит от того, сколько времени вы тратите на манипуляции со своим кодом. Особенно в сравнении с использованием кода для манипулирования данными.
Возможно, начиная новый проект, вы захотите использовать самый мощный из доступных языков, соблазнившись его инструментарием и возможностями по обработке данных. Но позднее вам придется проводить долгие часы, перерабатывая готовый код, зачастую с помощью самого примитивного инструмента — текстового редактора. Вам придется обращаться со своим сложноструктурированным кодом как с наименее структурированным типом данных — обычным текстом. А ведь именно этого способа обращения с данными в своем коде вы хотели бы избежать любой ценой. Ирония ситуации в том, что когда речь заходит о манипуляциях с самим кодом, то все остальные практики (например, обработка данных внутри выделенных контейнеров), работать не будут.
В некоторых популярных языках автоматический рефакторинг работает проще, но если вы хотите придать коду строгую продуманную структуру, то вам придется использовать редактор и VCS, иначе быстро запутаетесь. Есть любопытные проекты вроде
Lamdu, но ни один из них еще не дорос до уровня серьезного инструмента.
Итоги
Продумывая систему своего проекта и всех ее участников (людей и приложения), включая создание эффективного кода и долгосрочную поддержку, помните: менее мощный язык, как это ни парадоксально, дает больше возможностей. Как говорится, Slavery is freedom. Хотя, конечно, никто не отменял
баланс между выразительностью языка и целесообразностью.
Чем мощнее язык, тем выше нагрузка на программные инструменты, значит, им сложнее функционировать либо их возможности окажутся ограничены. В частности, снизится производительность компиляторов, а VCS и средства автоматического рефакторинга будут вам посредственными помощниками.
Нелегко придется и программистам, которые будут пытаться разобраться в коде или модифицировать его.
Логика подсказывает нам, что всегда нужно выбирать либо самые мощные решения, либо решения с избыточными для данной задачи возможностями. Лучше стараться поступать наоборот — находить менее мощное решение, способное решить вашу задачу.
К сожалению, этого никто не будет делать, пока создание новых языков будет требовать больших усилий. Так что намного эффективнее было бы выбирать такую программную экосистему, которая позволит легко создавать очень маленькие и слабые языки.