If I could export one feature of Go into other languages, it would be interfaces. — Russ Cox
Мой предельно простой компилятор Паскаля уже становился предметом двух публикаций на Хабре. Со времени их написания язык обзавёлся всеми недостающими средствами, положенными стандартному Паскалю, и многими плюшками, добавленными в Паскаль компанией Borland в её золотую пору. Компилятор также научился ряду простейших локальных оптимизаций, достаточных хотя бы для того, чтобы глаза не кровоточили при взгляде на листинг дизассемблера.
Тем не менее дебри объектно-ориентированного программирования остались совершенно нетронутыми. Так почему бы компилятору не послужить теперь полигоном для экспериментов в этой области? И почему бы нам не почерпнуть вдохновение из слов Расса Кокса, вынесенных в эпиграф? Попробуем реализовать в Паскале методы и интерфейсы в стиле Go. Затея интересна хотя бы тем, что все популярные в прошлом компиляторы Паскаля (Delphi, Free Pascal) по сути заимствовали объектную модель из C++. Любопытно посмотреть, как на той же почве приживётся совсем иной подход, позаимствованный из Go. Если вы вслед за мной готовы запастись изрядной долей иронии, отбросить вопрос «Зачем?» и воспринять происходящее как игру, добро пожаловать под кат.
Под «стилем Go» будем понимать несколько принципов, на основе которых внедрим методы и интерфейсы в Паскаль:
Для объявления методов и интерфейсов используются в новой роли стандартные ключевые слова Паскаля
Получатель фактически является первым аргументом метода.
Интерфейс представляет собой обычную запись Паскаля, в объявлении которой слово
Приведение к интерфейсному типу всегда делается явно:
При этом компилятор проверяет наличие всех методов, требуемых интерфейсом, и совпадение их сигнатур. Затем он устанавливает указатель
По сравнению с Go нынешняя реализация интерфейсов в Паскале имеет ограничения: нет возможности динамически запрашивать конкретный тип данных, который был приведён к интерфейсному типу. Соответственно, лишены смысла пустые интерфейсы. Возможно, следующим шагом в разработке будет восполнение этого пробела. Однако даже в нынешнем виде интерфейсы обеспечивают полиморфизм, полезный во многих не самых тривиальных задачах. Одну такую задачу мы и рассмотрим.
Неплохим примером использования интерфейсов может послужить программа рендеринга трёхмерных сцен методом обратной трассировки лучей. Сцена состоит из простых геометрических тел: параллелепипедов, сфер и т. п. Каждый луч, испущенный из глаза наблюдателя, требуется отследить (через все его отражения) до попадания в источник света или ухода в бесконечность. Для этого каждому виду тел приписывается метод
Вот так может выглядеть сцена, построенная описанным способом:
Весь исходный код программы, включая описание сцены, занимает 367 строк.
Простейшая реализация полиморфизма в собственном компиляторе Паскаля оказалась нетрудным делом, быстро принесшим первые плоды. Некоторых осложнений можно ожидать в задаче динамического определения конкретного типа данных, который был приведён к интерфейсному типу. Усилий потребует также устранение неочевидных конфликтов с механизмами проверки типов стандартного Паскаля. Наконец, помимо всех забот об интерфейсах продолжается неравная борьба с Microsoft вокруг ложных тревог их Windows Defender'а при запуске некоторых откомпилированных примеров.
Мой предельно простой компилятор Паскаля уже становился предметом двух публикаций на Хабре. Со времени их написания язык обзавёлся всеми недостающими средствами, положенными стандартному Паскалю, и многими плюшками, добавленными в Паскаль компанией Borland в её золотую пору. Компилятор также научился ряду простейших локальных оптимизаций, достаточных хотя бы для того, чтобы глаза не кровоточили при взгляде на листинг дизассемблера.
Тем не менее дебри объектно-ориентированного программирования остались совершенно нетронутыми. Так почему бы компилятору не послужить теперь полигоном для экспериментов в этой области? И почему бы нам не почерпнуть вдохновение из слов Расса Кокса, вынесенных в эпиграф? Попробуем реализовать в Паскале методы и интерфейсы в стиле Go. Затея интересна хотя бы тем, что все популярные в прошлом компиляторы Паскаля (Delphi, Free Pascal) по сути заимствовали объектную модель из C++. Любопытно посмотреть, как на той же почве приживётся совсем иной подход, позаимствованный из Go. Если вы вслед за мной готовы запастись изрядной долей иронии, отбросить вопрос «Зачем?» и воспринять происходящее как игру, добро пожаловать под кат.
Принципы
Под «стилем Go» будем понимать несколько принципов, на основе которых внедрим методы и интерфейсы в Паскаль:
- Не существует самостоятельных понятий класса, объекта, наследования.
- Метод можно реализовать для любого конкретного типа данных. Для этого не требуется изменять объявление самого типа.
- С интерфейсом совместим любой конкретный тип данных, для которого реализованы все методы, перечисленные в объявлении интерфейса. В объявлении конкретного типа данных не требуется указывать, что он реализует интерфейс.
Реализация
Для объявления методов и интерфейсов используются в новой роли стандартные ключевые слова Паскаля
for
и interface
. Никаких новых ключевых слов не вводится. Слово for
служит для указания имени и типа получателя метода (в терминологии Go). Вот пример описания метода для предварительно объявленного типа TCat
с полем Name
:procedure Greet for c: TCat (const HumanName: string);
begin
WriteLn('Meow, ' + HumanName + '! I am ' + c.Name);
end;
Получатель фактически является первым аргументом метода.
Интерфейс представляет собой обычную запись Паскаля, в объявлении которой слово
record
заменяется словом interface
. В этой записи не допускается объявлять никакие поля, кроме полей процедурного типа. Помимо этого, в начало записи добавляется скрытое поле Self
. В нём хранится указатель на данные того конкретного типа, который приводится к интерфейсному типу. Вот пример объявления интерфейса:type
IPet = interface
Greet: procedure (const HumanName: string);
end;
Приведение к интерфейсному типу всегда делается явно:
Pet := IPet(Cat);
При этом компилятор проверяет наличие всех методов, требуемых интерфейсом, и совпадение их сигнатур. Затем он устанавливает указатель
Self
, заполняет все процедурные поля интерфейса указателями на методы конкретного типа.По сравнению с Go нынешняя реализация интерфейсов в Паскале имеет ограничения: нет возможности динамически запрашивать конкретный тип данных, который был приведён к интерфейсному типу. Соответственно, лишены смысла пустые интерфейсы. Возможно, следующим шагом в разработке будет восполнение этого пробела. Однако даже в нынешнем виде интерфейсы обеспечивают полиморфизм, полезный во многих не самых тривиальных задачах. Одну такую задачу мы и рассмотрим.
Пример
Неплохим примером использования интерфейсов может послужить программа рендеринга трёхмерных сцен методом обратной трассировки лучей. Сцена состоит из простых геометрических тел: параллелепипедов, сфер и т. п. Каждый луч, испущенный из глаза наблюдателя, требуется отследить (через все его отражения) до попадания в источник света или ухода в бесконечность. Для этого каждому виду тел приписывается метод
Intersect
, вычисляющий координаты точки попадания луча на поверхность тела и компоненты нормали в этой точке. Реализация этого метода для разных видов тел различна. Соответственно, информацию о телах удобно хранить в массиве интерфейсных записей Body
, причём для всех элементов массива поочерёдно вызывается метод Intersect
. Интерфейс перенаправляет этот вызов на конкретный метод в зависимости от вида тела. Вот так может выглядеть сцена, построенная описанным способом:
Весь исходный код программы, включая описание сцены, занимает 367 строк.
Итоги
Простейшая реализация полиморфизма в собственном компиляторе Паскаля оказалась нетрудным делом, быстро принесшим первые плоды. Некоторых осложнений можно ожидать в задаче динамического определения конкретного типа данных, который был приведён к интерфейсному типу. Усилий потребует также устранение неочевидных конфликтов с механизмами проверки типов стандартного Паскаля. Наконец, помимо всех забот об интерфейсах продолжается неравная борьба с Microsoft вокруг ложных тревог их Windows Defender'а при запуске некоторых откомпилированных примеров.
KvanTTT
Заголовок и картинка не соответствуют содержимому статьи, так как об игре речи не идет.
Tereshkov Автор
Если вы прочли заголовок полностью, то, вероятно, неоднозначности не осталось.