РИНЦ Форум ВКонтакте LinkedIn

Идеология

Component, Object, Objective

Бывалому разработчику каждое из этих прилагательных что-то говорит.

Component

Циркулирует в среде сторонников Оберон и всех, на кого им удалось повлиять. Встречается в явном виде в Component Pascal и Component Object Model, отсылает к компонентно-ориентированному программированию:

Компонентно-ориентированное программирование (КОП = COP = component-oriented programming) возникло как своего рода дисциплина, т.е. набор определенных ограничений, налагаемых на механизм ООП, когда стало ясно, что бесконтрольное использование ООП приводит к проблемам с надёжностью больших программных комплексов. Это т.наз. проблема хрупких базовых типов [fragile base class problem]; проблема может проявиться при попытке изменить реализацию типа-предка, когда может оказаться, что изменить реализацию типа-предка даже при неизменных интерфейсах его методов невозможно, не нарушив корректность функционирования типов-потомков.

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

С точки зрения идеологии проекта, лучшим испытанием КОП был проект Mozilla и их XPCOM. И КОП это испытание не выдержало, затрещало по швам:

«Незамёрзшие интерфейсы». Такая «интересная» сущность появилась. С точки зрения идеологии ООП, это бред. Любой интерфейс примечателен тем, что его может реализовать вообще какой угодно разработчик, а, чтобы это могло быть так, конечно, он должен быть замёрзшим. Иначе как согласовать изменения интерфейсов с классами сторонних разработчиков. Там же всё поломается. Значит, на самом деле есть некоторые привиллегированные классы, которым только морально и позволено реализовать эти «незамёрзшие интерфейсы». А, значит, эти сущности правильнее было бы называть видами на класс. Слово вид отсылает к трёхуровневой системе типов языка Ада. В языке Ада есть типы, между которыми запрещено неявное приведение. Есть подтипы, между которыми неявное приведение работает, так как значения считаются тождественными, а отличаются только допустимые подмножества и машинные представления. Эти два уровня объективные, но после них есть ещё третий уровень, субъективный, и это виды. Во имя гибкости в будущих изменениях информация о типе может быть сокрыта, и тогда код, который работает со значениями типа, ВИДит только часть картины. Так же и в Mozilla XPCOM, есть классы, которые морально позволено реализовать только Mozilla, а «незамёрзшие интерфейсы» на самом деле суть виды на эти классы, логически привязанные к ним. С практической точки зрения это выражается, например, в том, что если установить Firefox, Thunderbird, Songbird, у них у всех будут отдельные библиотеки на файловой системе, несовместимые из-за мелких отличий в интерфейсах. А у iTunes и iCloud библиотеки общие, там ядро Objective-C.

Object

Прилагательное используется, когда добавляется ООП без каких-либо претензий на двоичную стабильность. Например, диалект Object Pascal и транслятор Object Ada. Впрочем, правило не строгое, и IBM Object COBOL, будучи основан сначала на IBM System Object Model, а затем на JVM, был на голову выше других object-трансляторов.

Беда семейства Паскаль-языков в том, что под тем или иным соусом, все яйца оказались в одной корзине. Язык Ада принципиально жёсткий, и тем силён в некоторых областях. Чтобы понять, насколько он жёсток, можно посмотреть, как устроено приложение к стандарту Распределённые системы. Программа запускается распределённо как бы сразу вся. Нельзя по частям что-то обновлять, иначе двоичная сериализация записей может в любом месте поломаться. А ведь на разные части программу разносят иногда именно для того, чтоб обновлять по отдельности. И стандарт Google Protobuf поднялся на том, что можно при логической связи компонент А—Б—В обновить А и В, а прокладку Б не трогать, но она всё равно будет насквозь пропускать неизвестные ей поля. Разумеется, и ООП в языке Ада жёсткое, требующее перекомпиляции. У Оберонов КОП — явное идеологическое решение. В Delphi/Object Pascal мыслительный процесс не особо прослеживается. Там как будто спинным мозгом всё управляется. Клиенты чего-то хотят, в их сторону делаются небольшие реверансы, а серьёзных НИОКР не ведётся.

Так все основные представители Большого Паскаля оказались в одной корзине и вместе разбились. Больше всего слышно оберонщиков, как их идейно обокрали, как деньгами залили Джаву. Да, про p-код верно, но гвоздь КОП в могиле Оберона — собственного производста, и вытаскивать его так никто и не собрался. В мире Джава была целая эпопея из Java Native Interface, Raw Native Interface, Java Runtime Interface, Java Native Access. Когда мы пытаемся понять, а где же Oberon Native Interface, и чем в Оберонах это кончилось, мы приходим к КОП, и всё становится понятно. В то время, как поддерживающая наследование и изменчивость базовых классов Java заменяла в IBM поддерживающую наследование и изменчивость базовых классов SOM, в то время, как в Apple поддерживающий наследование и изменчивость базовых классов Objective-C заменял поддерживающую наследование и изменчивость базовых классов SOM, в Microsoft Java и потом .NET заменяли не поддерживающий наследование COM, Оберон со своим КОП непоколебимо охраняет своё место на обочине истории.

Из этого вытекает намёк на другой ЯП. Давно уже переболел желанием делать свой ЯП, и меня бы устроил кодогенератор, как в COM и SOM, в комбинации с Адой. Те проблемы, которые меня волнуют, решаются не новым языком, в первую очередь. Видя ситуацию в целом, напрашивается предположение, что язык делать всё же придётся. Но тут особый случай.

  • Обычно новый ЯП попадает в жесточайшую конкуренцию с другими. А тут всё наоборот. Тут всё, что пишется на этом языке, через автоматические генераторы привязок стыкуется с почти любым другим языком программирования. И библиотеку на стороннем языке программирования если состыковать с этим новым, то средствами ядра платформы, а это автоматом также стыкует и с другими языками, для которых есть генераторы привязок.
  • Если распространению платформы будет вредить отсутствие родного языка, то как можно это терпеть.
  • Как минимум один полуродной ЯП запланирован, а именно, Objective-C, и он, будучи поставлен на другие рельсы, не может быть точно похож на оригинал. Языком меньше — языком больше.

Objective

Исторически происходит от названия Objective C. Изначально в его названии не было дефиса, а ещё у него было как минимум 4 версии, так что Objective-C 2.0 на самом деле пятая или шестая версия. Исторически каждый раз, как создавалось нечто с таким прилагательным в названии, это предполагало повышенную совместимость с этим языком. Например, Objective Modula-2 наводит на мысли, что это процедурная Modula-2, к которой ООП добавили не как в Оберонах, а совместимо с Objective-C, и это действительно так.

Довольно долго Objective C был ненамного лучше своих object-аналогов. Можно считать, наполовину лучше: можно добавлять методы, но не поля. Но в версии Objective-C 2.0 (≈ 2006) и с полями тоже порешали, там это называется нехрупкое ABI. Objective PE, хотя и ведёт преемственность от более стройной IBM SOM, содержит в своём названии objective по нескольким причинам:

  1. Как и в Objective-C 2.0, SOM и идейные преемники, обеспечивают двоичную стабильность. У нас это было на 15 лет раньше, в 1991м.
  2. Однако ничего из слов System Object Model или SOM не успело примелькаться.
  3. То же можно сказать о названии книги Putting Metaclasses to Work.
  4. Планируется ограниченная совместимость с Objective-C на уровне исходных кодов, для портирования Foundation и AppKit.
  5. Планируется мост в Cocoa на макОСе.

Таким образом, связь с Objective-C имеется достаточно прямая, хотя мы не ложимся под ядро Objective-C, как это делают Objective Modula-2. Нас там не устраивает плоское пространство методов, не положенных явно внутрь классов. Также не устраивает странноватый способ именования методов от Смоллток. В нашей Аде нам не были нужны эти странные имена, чтоб вызывать методы красиво. У нас в агрегатах («конструкторах») записей синтаксис позиционных и именованных полей. И для агрегатов массивов такой синтаксис тоже доступен. У нас при специализации обобщённых пакетов и процедур есть позиционные и именованные параметры. И, конечно, при вызове всё это тоже есть. Так что после Ады принудительное указание имён-как бы-не имён в вызовах методов выглядит странным и не очень удобным. В нашей Аде порядок поменять можно, а в Objective-C — нельзя. И мы знаем, что этот ни на что не похожий синтаксис напрягает всех, кто с Objective-C имел дело. Так что его мы сбросим. Есть стандарт преобразования имён между SmallTalk и CORBA. Есть стандарт преобразования имён, принятый самим Apple, для Cocoa-Java, с прописанными самим Apple исключениями. Есть BridgeSupport для сценарных языков, и одной из его задач является коррекция автоматом преобразованных имён. Все три опции планируется рассмотреть и сравнить, чтоб понять, как лучше сделать проекцию.

SOM, PMtW, CLOS, Python

IBM System Object Model

System Object Model наравне с Objective-C 2.0 — идейные предшественники Objective PE. Работа над SOM была Роковым для SOM стал 1997й год. Двумя столпами SOM были IBM и Apple. IBM решительно бросает все силы на Java и отменяет OS/2. Apple терпит неудачу с Mac OS 8 Copland, возвращает Стива Джобса, а он за собой паровозом из NeXTSTEP тащит Objective-C. Жалкие осколки былого величия Apple SOM растворяются в Carbon, а именно в Human Interface Toolbox. Там было странноватое минималистичное ООП, чтоб дать портировать код с Apple Mac OS Classic.

Распространялся SOM в том числе и на Windows, и до сих пор можно запустить на Windows 10. При этом SOM как есть не приходится называть достойным возрождения. В нём нет ARC, как в Objective-C 2.0. В Objective-C ARC тоже далеко не сразу появился, ведь там, как и в других инструментах, и ручного RC-то не было. Но им занимались, он этот путь прошёл, программы адаптировались, а развитие SOM оборвалось в 1997м. Кроме того, в IBM пытались заигрывать интеграцией с C++. А в C++ нет RC, вот и в SOM его не добавляли в ядро, зато в SOM 3.0 SOMObject можно увидеть кучу разных копирующих конструкторов, волатильных-неволатильных, константных-неконстантных, и присвоение somAssign* тоже. За прошедшее время гибридизацией C++ успели позаниматься и в Apple (Objective-C++), и в Microsoft, аж три раза: C++/MX, C++/CLI, C++/CX. Кстати, работала в Microsoft Jenniffer Hamilton, после IBM Direct2SOM C++. Эти попытки гибридизации можно разбить на две группы, по градусу фанатичности в попытке подстроиться под C++: D2SOM C++ и C++/MX; и все остальные. Под .NET получилось две попытки, и переделывать пришлось, потому что первый раз была шляпа. Фанатичность сошла на нет, и в дальнейшем в этих гибридах просто делали две паралелльные иереархии классов. А раз получается шляпа, то и зачем подстраивать объектную модель под C++? SOM был отменён раньше этих прозрений, и эволюционно постепенно ремонтировать было некому. До кучи мелочи вроде отсутствия Юникода. Так что SOM как он есть выбрасываем без сожалений, берём только лучшие практики. В научной статье «Общая платформа исполнения приложений» ради экономии времени вариант брать SOM как есть ещё рассматривался всерьёз, сейчас понятно, что это не вариант.

Putting Metaclasses to Work

Сотрудники IBM, работавшие над SOM, не успели воплотить свои идеи о будущем развитии, но поделились своими идеями в этой книге (1998). Главное нововведение — это так называемые кооперативные методы. В SOM, как и в C++, при множественном наследовании наследники вызывают унаследованные методы от родителей, и если их несколько, то и вызовов нужно сделать несколько. При ромбовидном наследовании это может вызывать экспоненциальный рост количества вызовов, а раз так, то эта возможность становится плохо применимой. Ей просто не пользуются. В методах типа somAssign* эта брешь была заткнута битовыми масками, а для других методов в SOM ничего не было. Сотрудники IBM были людьми образованными и эрудированными, и заменили механизм C++ на механизм CLOS: для каждого класса его предки выстраиваются в порядок разрешения методов (MRO, Method Resolution Order). Реализация каждого заменённого метода пересылает вызов по цепочке не в конкретный родительский класс, а абстрактному «следующему по порядку», который может быть даже и не родителем, а братом.

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

Книга сопровождается исходниками на Java реализации модели. По задумке авторов, Java была на века, и, выбрав Java, они вошли в вечность. По иронии судьбы, на современной Java их код не компилируется, а если устранить проблемы с синтаксисом, не запускается. Ту версию Java, где всё хорошо, ещё попробуй найди. А вот бинарники SOM и транслятора Direct2SOM C++ на Windows 10 работают. Эх, не на ту лошадь вы, ребята, ставку сделали.

Common Lisp Object System

Примечательна выше упомянутым механизмом вызовов методов при множественном наследовании. Только там это называется линеаризацией классов. Но вот с метаклассами там беда, и имя ей несовместимость метаклассов. Так происходит, когда класс заказывает себе в метаклассы такой класс, который не подкласс родительских, и это фатально. В System Object Model 2.0 было принято такое элегантное решение этой проблемы, как принудительное скрещивание метаклассов, если нужного сходу нет. В CLOS без этого механизма метаклассы далеко не столь удобны. В большом проекте это как сидеть на пороховой бочке. А раз так, то и применяют их меньше.

Кроме того, в CLOS странноватая объектная модель. Методы не лежат внутри классов. Вместо этого они лежат рядом с классами, внутри пакетов. Вернее, в пакетах лежат контейнеры, а в эти контейнеры забрасываются реализации, и таким образом сделана множественная диспетчеризация. Казалось бы, если согласились на МН, то гулять-так гулять, пусть и МД тоже, нет? Изучал этот вопрос, думал, всё же решил, что нет. Посмотрите, чем придётся заниматься ядру. Я почитал, и мне это в ядро тащить перехотелось. Плюс, всё, что там описано, можно сделать постфактум поверх ядра без МД, если кому-то так будет надо.

Python

Автор языка Гвидо ван Россум никогда не скрывал, что впечатлялся книгой PMtW в Python 2.2. А в Python 3.0 наконец стало можно по-человечески вызывать родительские реализации методов. С другой стороны, впечатление от книги PMtW, видимо, было недостаточно сильным, и в Питоне предана идея SOM 2.0, перекочевавшая в книгу: принудительное скрещивание метаклассов. Питон богат декораторами, но конкретно этот языковой механизм так глубоко укоренён, что никаким декоратором или __методом__ проблему изнутри не починить, что только ни пробовали. Это может сделать только автор языка, и он всё никак не соберётся. Поэтому метаклассы в Питоне были и есть болезненные в использовании, поэтому применяют их мало.

Вывод

PMtW лучше всех. Но её реализации в природе почти никогда не было. Авторским интерпретатором на Джаве неизвестно, чтоб в каком-либо проекте пользовались. Так что если мы берём PMtW, то ЗАРЕШАЕМ.

С другой стороны, а как можно понять, что ЗАРЕШАЕМ? И тут можно поблагодарить SOM и Python за то, что обкатали отдельные подмножества возможностей PMtW.

Protected Executable (COFF)

Под таким названием комитет Tool Interface Standard принял стандарт на формат исполняемых файлов, применяемый в Microsoft Windows. Стандартов у этого комитета было несколько, среди них и ELF, и OMF. Ведущую скрипку, в комитете играл Intel, а были кроме Microsoft, например, и Borland. Это всё происходило в 1990х, а все новые дополнения вроде AMD64, ARM, подпись программ Authenticode описаны только в документах Microsoft.

Проект исходит из того, что мы берём инструменты разработки как есть, и пытаемся заиграть ими на новый лад. То есть, можно PE взять, запустить на макОСе, а можно ELF взять, запустить на Windows. Гипотетически так, а практически каждая целевая платформа оставляет родимые пятна. И PE тут получается как минимальное зло.

ELF используется в Linux, BSD и других, и с ними есть две гигантские проблема под названием fork/exec, гигантская хоть вместе, хоть по отдельности, плюс, прерывания. Если для PE/COFF на макОСе достаточно процесса в пользовательском пространстве, то для запуска ELF пришлось городить аж целую виртуализацию в гипервизоре. С многопоточностью fork не в ладах. А ещё это кошмарное наследование открытых дескрипторов. Это то, что может произойти нечаянно! Например, из жизненной практики: многопоточный Ada Web Server редактирует конфигурацию подчинённых служб и перезапускает их командой service sniproxy restart. Сокеты вебсервера на портах 80 и 443 нечаянно наследуются /sbin/service, а от него их нечаянно наследует sniproxy. Потом, когда надо перезагрузить AWS, выясняется, что его порты уже заняты sniproxy, и AWS не может запуститься. sniproxy, конечно же, не знает, как отвечать вместо AWS. Какой же это всё бред! Много к Microsoft может быть претензий, но у них, наоборот, чтоб такое сложное действие совершить, нужно специально заморочиться вызовом DuplicateHandle. Не ошибёшься нечаянно. Нет.

В Windows, а, значит, в PE/COFF, единая для всех структурная обработка исключений. Если происходит аппаратная исключительная ситуация, обработчик будет найден, и это будет правильный обработчик. Standard.Storage_Error в языке Ада, System.SysUtils.EAccessViolation в Delphi. И всё это может спокойно происходить в разных потоках. Пока не переходишь на Линукс, это воспринимается как данность, а там всё совсем не так. В Линуксе глобальные для всего процесса обработчики сигналов, царит анархия. Ada, Delphi и все остальные норовят поставить свой глобальный обработчик, который другому языку программирования не подходит, и нет способа сделать так, чтоб они не толкались локтями.

В Windows все вызовы только через kernel32.dll или ntdll.dll, а интерфейс ядра syscall не документирован и меняется. В Linux любой процесс может напрямую обратиться к ядру, а это возьми да и окажись ядро совсем не той операционной системы. И как это из пространства пользователя прикажете перехватывать?

Таким образом, PE оказывается более привлекательным сырьём. Только не надо это путать с WinAPI. От WinAPI надо уходить.