В предыдущей статье речь шла о проблеме ООЯП в общем, в данной статье я бы хотел развить эту тему и показать более конкретно проблемы ООЯП.
Основные идеи: ООЯП имеют две ключевые проблемы: неполноценная объектная парадигма и преждевременная типизация. Неполноценная объектная парадигма не даёт определения понятию нетипизированной объектной композиции (композиция является важнейшим элементом любой парадигмы). Преждевременная типизация ограничивает семантику абстрактных понятий (семантических абстракций).
Неполноценная объектная парадигма
Объектная парадигма в явном виде не определяет базовых механизмов. Любая парадигма должна определять:
Примитив абстракции – определяет стиль мышления парадигмы и является базовым элементом моделирования.
Композиция примитивов – позволяет описывать (моделировать) системы произвольной сложности. Сама композиция также сама должна являться примитивом.
Агрегирующее отношение – абстрактный механизм, который связывает примитивы в композиции. Точно такой же механизм проявляет примитив и во внешнем взаимодействии.
Глобальное состояние – где (но не как!) будет локализовано абстрактное (наблюдаемое) состояние системы. Это свойство не сколько парадигмы, сколько следствие вычислительной архитектуры. Результат выполнения любой модели можно обнаружить лишь по изменению состояния в самом общем смысле.
Принципиально, что парадигма определяется в нетипизированном виде. Чтобы пользоваться парадигмой (не путать с использованием ЯП, основанных на данной парадигме), типизация не нужна. Парадигма должна лишь определять, как и на каких механизмах строится модель (т.е. одна или несколько композиций).
Ещё более важно, что композиция, а не примитив, является "кирпичом" при моделировании в силу сложности моделируемых систем. Композиции являются чистыми семантическими абстракциями, без привязки типов или любых других "ресурсов". Семантическая абстракция – это понятие, заданное только своим именем, причём имя в произвольном виде, слово или словосочетание.
Для примера, функциональная парадигма полностью удовлетворяет этим свойствам (то же самое можно показать и для процедурной/императивной парадигмы):
Примитив абстракции: функция – отображение некоторого входа в некоторый выход.
Функциональная композиция: цепочка функций (отображений).
Агрегирующее отношение: выход одной функции подаётся на вход другой. Т.е. это связь в композиции, и вызов функции как таковой.
Глобальное состояние: аргументы функций.
В ООП и текущих определениях объектной парадигмы, объектная композиция в лучшем случае подразумевается, в худшем её вообще нет. Так называемые постулаты ООП (абстракция/инкапсуляция/наследование/полиморфизм) – это отвлечённый набор понятий, причём два последних вообще относятся к типизации и к объектной парадигме не имеют никакого отношения. Всё есть объект. Объекты взаимодействуют между собой. – тут объектная композиция по крайней мере подразумевается, и как-то логически её можно вывести.
Кончено, в ООЯП технически можно увидеть композиции в том или ином виде, но объектная композиция должна быть базовым и основным механизмом ООЯП, а не опциональным и второстепенным.
Как должна примерно выглядеть объектная парадигма (по аналогии с функциональной):
Примитив абстракции: объект – агент, поддерживающий некоторый протокол взаимодействия. Протокол подразумевает правила и семантику взаимодействия для обеих сторон.
Объектная композиция: граф объектов, поддерживающих протоколы друг друга. Сам граф является объектом с собственным протоколом.
Агрегирующее отношение: поддержка протокола объекта (т.е. понимание протокола и следование ему). Способность поддерживать протокол друг друга связывает объекты в композицию, равно как и внешнее взаимодействие с конкретным объектом возможно лишь в случае поддержки его протокола.
Глобальное состояние: внутреннее состояния объекта.
Отличительной особенностью объектной парадигмы является то, что объект, в отличие от функции и процедуры, не является чисто трансформирующим (т.е. меняющим состояние) примитивом. Т.к. глобальное состояние распределено по объектам (в ООЯП это инкапсуляция), протокол объекта становится способом изменения состояния. Это должно быть отражено на уровне объектной парадигмы.
Для этого объектная парадигма может использовать любую парадигму с трансформирующим примитивом, собственно функциональную и/или процедурную парадигмы для протоколов объектов. Обе парадигмы используются в таком же абстрактном нетипизированном виде. Если протокол объекта основан на функциональной парадигме, мы получаем немутабельный объект, если же на процедурной – то мутабельный.
Преждевременная типизация
Типизация (система типов) в конечном счёте нужна для единственной цели: формальной верификации корректности программ. Правила задаются через грамматику ЯП, транслятор на основе грамматики проводит собственно верификацию.
Преждевременность типизации заключается в том, что ООЯП (класс-ориентированные) делают сильный акцент на использовании типов, на трактовке классов как типов. Первое что бросается в глаза (причём как на практике при использовании ООЯП, так и в учебниках/книгах при описании ООЯП) – система типов ООЯП, хотя по идее должна быть объектная парадигма с её композицией и семантическими абстракциями. Ссылочные типы и типы значений, полиморфизм подтипов, наследование, принцип подстановки Лисков (LSP), перегрузка методов, обобщения (generics), абстрактные типы данных, функциональный тип и ФВП как элементы функционального программирования, вывод типов, приведение типов, абстрактные классы и интерфейсы – все эти понятия являются следствием типизации.
С одной стороны, типизация неизбежна, т.к. только корректные программы можно выполнить. Но с другой, появление типизации вводит ограничения на семантику абстракций. Идея в том, чтобы появление типизации отложить на столько позже, на сколько это возможно. Другими словами, формальную верификацию корректности проводить только после того как определена объектная композиция. В идеале, типизация должна быть подключаемой, её место где-нибудь перед этапом компиляции, и уже на этапе компиляции собственно и выполняется верификация.
Если посмотреть на типизацию более широко, то преждевременная типизация в ООЯП проявляется также и в фиксированном толковании абстрактных механизмов объектной парадигмы в виде конкретных типов реализации. Показательный пример – протокол объекта (он же контракт) жёстко реализован в виде методов с фиксированными структурой и поведением (параметры, возвращаемое значение) как одна из возможных типов реализаций протокола объекта. Причина добавления модификаторов методов async/wait в C# — видимо стало понятно, что жёсткая (синхронная) связь вызова метода и возвращаемого значения не соответствует практическим задачам. На самом деле это исправление последствий преждевременной типизации. Опять же, в идеале конкретный тип реализации протокола должен быть подключаемым, с конкретной реализацией любого механизма.
Идея подключаемой типизации мне представляется в виде нескольких этапов (это не какой-то водопадный процесс разработки ПО, все этапы происходят при написании кода, не выходя из IDE).
Описывается объектная модель (одна/несколько нетипизированных объектных композиций), которая содержит только семантические абстракции. Объекты, их протоколы, композиции объектов – всё неформально описывается в виде семантических абстракций. На этом этапе смысл объектной модели следует из самих понятий.
Затем происходит атрибутизация, т.е. определение (привязка) нетипизированных атрибутов для семантических абстракций. В результате получаются атрибутированные семантические абстракции. Атрибуты – это не поля данных, свойства и т.п. из ООП/ООЯП, атрибуты определяют смысловую структуру понятия. На этапе атрибутизации вообще нет понятия состояния.
Следующий этап – абстрактная (нетипизированная) реализация протоколов объектов (говоря традиционно – реализация методов). Для протоколов объектов выбирается парадигма реализации с трансформирующими примитивами (функциональная и/или процедурная). На этом этапе уже доступны атрибуты, но они (как и абстрактные функции и/или процедуры) всё ещё семантические абстракции. Использование трансформирующих парадигм на этом этапе подразумевает абстрактное состояние объектов (раз есть что трансформировать), и структура этого состояния как раз и вытекает из атрибутов объектов.
Далее идёт конкретная (типизированная) реализация – именно тут происходит привязка типов к объектам и их атрибутам, а также параметрам/аргументам в протоколах объектов. Абстрактные процедуры/функции абстрактной реализации протоколов объектов конкретизируются до традиционных конструкций типа методы класса.
Отдельно отмечу, что привязка типов происходит через инжектирование выражений, а не указание имени типа. Другими словами, тип выводится из типа выражения, что гарантирует явную инициализацию.
После этапа конкретной реализации все понятия типизированы, и можно проводить формальную верификацию (как часть трансляции).
Заключение
Обозначенные проблемы, на мой взгляд, являются причиной того, что ООЯП фундаментально противоречивы. Неудивительно, что ООЯП и ООП в их текущем виде всегда будут объектом критики.
Есть ещё и третья проблема, но она касается не только ООЯП, но и других языков: это синтаксис, основанный на текстовой грамматике. В настоящее время поддержка текстовых грамматик в IDE настолько развита, что возникает вопрос: а зачем требуется текстовое представление кода? IDE манипулируют целыми структурными блоками типа методов или выражений, что текст как такой вырождается. Сравнивать версии в системах контроля версий как текст? Но это лишь вопрос реализации поддержки в IDE.
Синтаксис, основанный на текстовой грамматике ограничивает мета-возможности по той же причине, что мета-уровень требует от IDE дополнительной поддержки, и нет особого смысла задавать их в виде текста. Т.е. мета-возможности реализуются только на уровне IDE. По сути IDE и есть грамматика.
Поэтому глобальная идея – разработать объектно-ориентированный язык (т.е. IDE) на основе внятной объектной парадигмы, без текстовой грамматики и с развитыми мета-возможностями трансформации объектных композиций в читаемый код.