Паскаль играет в Go. Реализация методов и интерфейсов в любительском компиляторе

    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» будем понимать несколько принципов, на основе которых внедрим методы и интерфейсы в Паскаль:

    • Не существует самостоятельных понятий класса, объекта, наследования.
    • Метод можно реализовать для любого конкретного типа данных. Для этого не требуется изменять объявление самого типа.
    • С интерфейсом совместим любой конкретный тип данных, для которого реализованы все методы, перечисленные в объявлении интерфейса. В объявлении конкретного типа данных не требуется указывать, что он реализует интерфейс.

    Реализация


    Для объявления методов и интерфейсов используются в новой роли стандартные ключевые слова Паскаля 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;

    При преобразовании конкретного типа к интерфейсному компилятор проверяет наличие всех методов, требуемых интерфейсом, и совпадение их сигнатур. Затем он устанавливает указатель Self, заполняет все процедурные поля интерфейса указателями на методы конкретного типа.

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

    Пример


    Неплохим примером использования интерфейсов может послужить программа рендеринга трёхмерных сцен методом обратной трассировки лучей. Сцена состоит из простых геометрических тел: параллелепипедов, сфер и т. п. Каждый луч, испущенный из глаза наблюдателя, требуется отследить (через все его отражения) до попадания в источник света или ухода в бесконечность. Для этого каждому виду тел приписывается метод Intersect, вычисляющий координаты точки попадания луча на поверхность тела и компоненты нормали в этой точке. Реализация этого метода для разных видов тел различна. Соответственно, информацию о телах удобно хранить в массиве интерфейсных записей Body, причём для всех элементов массива поочерёдно вызывается метод Intersect. Интерфейс перенаправляет этот вызов на конкретный метод в зависимости от вида тела.

    Вот так может выглядеть сцена, построенная описанным способом:



    Весь исходный код программы, включая описание сцены, занимает 367 строк.

    Итоги


    Простейшая реализация полиморфизма в собственном компиляторе Паскаля оказалась нетрудным делом, быстро принесшим первые плоды. Некоторых осложнений можно ожидать в задаче динамического определения конкретного типа данных, который был приведён к интерфейсному типу. Усилий потребует также устранение неочевидных конфликтов с механизмами проверки типов стандартного Паскаля. Наконец, помимо всех забот об интерфейсах продолжается неравная борьба с Microsoft вокруг ложных тревог их Windows Defender'а при запуске некоторых откомпилированных примеров.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 3

      0

      Заголовок и картинка не соответствуют содержимому статьи, так как об игре речи не идет.

        0
        Если вы прочли заголовок полностью, то, вероятно, неоднозначности не осталось.
        0

        Интересные эксперименты! Продолжайте

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое