Предыдущая серия:
Понятия/концепции, из которых конструируются программистские парадигмы, связываются друг с другом отнюдь не произвольно, на субъективное усмотрение человека-инженера. Тут существуют достаточно формальные принципы, которые сформулировал профессор Matthias Felleisen в книге "How to Design Programs", своеобразном идеологическом собрате SICP (в этой линейке после SICP также рекомендую его же книги "Litthe MLer" и "Litthe Schemer"). Практически независимо от него они были открыты и профессором Van Roy-ем из бельгийского Catholic University of Louvain в книге "Concepts, Techniques, and Models of Computer Programming", и на основе этого всего и сделан данный цикл статей и готовятся курсы.
Общая идея, что некоторая парадигма, сформированная из определённого множества концепций, не должна приводить к усложнению языка программирования (или архитектуры системы, опирающейся на эту парадигму), иначе он может усложнить решение задач, на которые ориентирован. Если так случается, значит парадигма непригодна, а точнее -- существует некая концепция, которая тут подходит лучше, но пока она, возможно, ещё не открыта.
Три примера того, как открываются концепции программирования, и как они помогают формировать новые парадигмы.
1) Если в системе, где существуют лишь вычисления (функции), понадобится обновляемая память (какие-то дополнительные промежуточные результаты вычислений), каждой функции надо будет добавить по два параметра (входное и выходное значения промежуточного объекта-"памяти"). Это громоздко и не модульно, так как работа с памятью размазывается по всему коду программы. Но такая неуклюжесть легко ликвидируется добавлением концепции именованных состояний (переменных).
2) Если требуется смоделировать несколько независимо работающих активностей в системе, нужно реализовать независимые стеки исполнения, планировщик и механизм переключения активностей. Вся эта сложность исключится, если мы добавим в язык концепцию параллельности.
3) Если требуется организовать универсальную обработку ошибок, когда любая функция может обнаружить ошибку и передать управление некоторой внешней процедуре обработки ошибок, то придётся добавлять все коды ошибок в результаты всех функций и организовывать множество проверок вызовов всех функций, не возвращают ли они код ошибки. Вся эта сложность убирается, если мы добавим в язык концепцию исключительных ситуаций.
Общее в этих трех сценариях (и многих других!) заключается в том, что нам необходимо выполнить некоторую повсеместную (нелокальную) модификацию программы для поддержки новой концепции. Существует даже особая парадигма аспектно-ориентированного программирования, ориентированная на изоляцию и организацию в виде модулей любых локальных концепций, которые напрашиваются быть "размазанными" по всему проекту (например, вызовы логирующих функций, которые обычно приходится включать внутрь многих функций).
Если в ряде проектов регулярно проявляется потребность в поддержке некоторой "повсеместной" практики, то это вполне может быть признак того, что мы стали свидетелями новой концепции программирования, ожидающей открытия. Ну или просто специфика именно вашей линейки проектов, схожих задач такова, что в ней возникла какая-то локальная полезная концепция, которую желательно выделить явно.
Причём реализовывать её правильнее всего не в виде приляпок для стандартных библиотек, а в виде новой полноценной фичи используемого языка. Ну или хотя бы как универсальный паттерн, например:
https://vk.com/wall-152484379_707
Тогда мы больше не будем нуждаться в повсеместных модификациях проекта, и восстановим простоту программы.
Алан Кэй, напомню, вывернул эту схему наизнанку, и достиг тысячекратной компактности кода в сравнении с проектами мэйнстрима. В 20 тысячах строк кода его коллектив реализовал и полноценную ОС, и низкоуровневые кроссплатформные интерфейсы с железом, и прикладные пакеты уровня MS Office, и браузер, и т. п. Например, вся графическая подсистема уместилась в 300 строках кода.
https://www.itweek.ru/idea/blog/idea/477.php
https://www.jetbrains.com/ru-ru/mps/concepts/domain-specific-languages/
Кэй смог этого добиться за счёт контринтуитивного навыка видеть в архитектурных слоях разрабатываемой системы свои "языки программирования" (в мэйнстриме они известны как предметно-ориентированные языки, domain specific languages, DSL), наиболее компактно и продуктивно решающие профильную задачу. Он назвал свой подход "вычисляемой математикой", по сути, создавая под классы задач свои DSL с оптимальным именно для них набором программистских концепций. Это ведь можно делать не только для императивного или функционального программирования "в общем", но и для типичных прикладных задач (например, разработка GUI или проектирование распределённых систем).
Я много лет разбирался в том, как же он это делает :) и вот наконец понял, въехав как следует в формализм CTP и HDP. Нюанс ещё и в том, что у Кэя не было сегодняшних технологий, ему пришлось под свои задачи создавать тяжёлый фреймворк OMeta, а у нас уже есть например отличный мультипарадигмальный язык Julia с нативной поддержкой DSL-ей и метапрограммирования (формально самой сильной на сегодня парадигмой разработки).
На стратегическую тему "как понять в программировании всё/как проектировать программы с тысячекратной компактностью кода", с изучением конкретных практик программирования, я готовлю цикл курсов, напомню, по плану https://skillsmart.ru/roadmap/
первый из них должен выйти уже в этот осенне-зимний период.
В следующей заметке познакомимся уже более подробно и детально с четырьмя ключевыми концепциями в программировании (включая две фундаментальные концепции из предыдущего занятия).
продолжение следует