Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Компилятор Glasgow Haskell

Глава 5 из книги "Архитектура приложений с открытым исходным кодом", том 2.

Оригинал: The Glasgow Haskell Compiler
Авторы: Simon Marlow и Simon Peyton-Jones
Перевод: Н.Ромоданов

5.4. Средства расширяемости

Часто бывает, что проект живет или умирает в зависимости от того, насколько он расширяем. Монолитный кусок программного обеспечения, которой не является расширяемым, должен делать все и должен делать это правильно, в то время как расширяемый фрагмент программного обеспечения, может быть полезным даже в случае, если в нем не еще реализованы сразу «из коробки» все необходимые функции.

Проекты с открытым исходным кодом, конечно, расширяемые по определению, причем каждый разработчик может взять код и добавить в него свои собственные функции. Но модификация оригинального исходного кода проекта, осуществляемая кем-то еще, не только требует больших накладных расходов, но также не способствует распространению ваших расширений среди других пользователей. Поэтому в успешных проектах, как правило, предоставляются свои собственные варианты расширений, которые не связаны с изменением основного кода, и в этом отношении компилятор GHC не является исключением.

Правила преобразований, определяемые пользователями

Ядро компилятора GHC представляет собой длинную последовательность проходов оптимизации, каждый из которых выполняет некоторое преобразование из Core в Core, сохраняющее семантику. Но автор библиотеки определяет функции, для которых часто требуются некоторые свои собственные нетривиальные предметно-ориентированные преобразования, которые никак не могут быть предсказаны компилятором GHC. Итак компилятор GHC позволяет авторам библиотек определять правила переписывания (rewrite rules), которые применяются для того, чтобы преобразовывать программы в процессе оптимизации [PTH01]. Таким образом, программисты могут, по сути, расширять компилятор GHC с помощью предметно-ориентированных вариантов оптимизации.

Одним из примеров является правило foldr/build, которое выражается следующим образом:

{-# RULES "fold/build"    
    forall k z (g::forall b. (a->b->b) -> b -> b) . 
       foldr k z (build g) = g k z
 #-}

Все правило является параметром pragma, которое вводится с помощью {-# RULES. В правиле говорится, что всякий раз, когда компилятор GHC видит выражение (foldr k z (build g)), он должен переписать его как (g k z). Это преобразование сохранит семантику, но что это именно так, нужно изучить статью [GLP93], так что нет никаких шансов, что компилятор GHC выполнит это правило автоматически. Если воспользоваться еще несколькими другими правилами и некоторыми параметрами INLINE pragma, то компилятор GHC сможет сливать вместе функции преобразования списков. Например, два цикла (map f (map g xs)) объединяются в один.

Хотя правила переписывания просты и ими легко пользоваться, они оказались очень мощным механизмом расширения. Когда мы десять лет назад впервые добавили эту возможность в компилятор GHC, мы предполагали, что это будет полезная возможность, которой будут пользоваться лишь изредка. Но на практике она оказалась полезной в очень многих библиотеках, эффективность которых часто во многом зависит от правил переписывания. Например, в собственной библиотеки компилятора GHC base содержится свыше 100 правил, а в популярной библиотеке vector используется несколько десятков правил.

Плагины компилятора

Один из способов, с помощью которого можно расширять компилятор, это разрешить программистам писать проход компиляции, который будет вставлен непосредственно в конвейер, используемый в компиляторе. Такие проходы часто называются «плагинами». В GHC плагины поддерживаются следующим образом:

  • Программист, скажем, пишет проход из Core в Core, в виде обычной функции языка Haskell в модуле P.hs и компилирует его в объектный код.
  • При компиляции модуля программист использует флаг командной строки -plugin P. Либо он может в начале модуля задать флаг в параметре pragma.
  • GHC ищет модуль P.o, динамически компонует его с работающим двоичным модулем GHC и вызывает модуль в соответствующем месте в конвейере.

Но что такое «соответствующее место в конвейере»? Компилятор GHC этого не знает, и поэтому позволяет плагину принять соответствующее решение. Из-за этого и ряда других причн, интерфейс API, который должен быть реализован в плагине, несколько сложнее, чем просто функция, действующая из Core в Core, но не более.

Для плагинов иногда требуются вспомогательные данные, либо плагины сами могут создавать такие данные. Например, плагин может выполнить некоторый анализ функций в компилируемом модуле (скажем, в модуле M.hs), и, возможно, есть смысл поместить эту информацию в интерфейсный файл M.hi, с тем чтобы у плагина был доступ к этой информации в случае, когда компилируются модули, в которые импортируется модуль M. Для поддержки такой возможности в компиляторе GHC предоставляется механизм аннотаций.

Плагины и аннотации являются относительно новыми возможностями в компиляторе GHC. Они имеют более высокий порог вхождения, чем правила переписывания, поскольку плагин работает с внутренними структурами данных компилятора GHC, но с их помощью, конечно, можно делать гораздо больше. Еще предстоит выяснить, насколько широко будут они использоваться.

Компилятор GHC как библиотека: интерфейс API компилятора GHC

Одной из первоначальных целей разработки компилятора GHC было создание модульной базы, на основе которой можно было бы строить все остальное. Мы хотели, чтобы код GHC был максимально прозрачным и настолько хорошо документированным, насколько это было возможным, для того, чтобы его могли использовать другие разработчики в качестве основы для исследовательских проектов; нам казалось, что другие разработчики захотели бы внести в компиляторе GHC свои собственные изменения с тем, чтобы добавить новые экспериментальные возможности или варианты оптимизации. Действительно, было несколько примеров таких изменений: например, есть версия GHC с языком Lisp в качестве входного интерфейса, а также версия GHC, которая генерирует код на языке Java, причем обе разработки делались совершенно независимо лицами, которые очень слабо контактировали или совсем не контактировали с командой разработчиков компилятора GHC.

Однако создание модифицированных версий компилятора GHC представляет собой лишь небольшую часть из тех приложений, в которых можно повторно использовать код компилятора GHC. По мере того, как растет популярность языка Haskell, наблюдается увеличение потребности в инструментальных средствах и инфраструктуре, которые должны уметь анализировать исходный код на языке Haskell, и в компиляторе GHC, конечно, есть много функций, необходимых для создания подобных инструментальных средств: парсер (синтаксический анализатор) языка Haskell, абстрактный синтаксис, средства проверки типов и так далее.

Имея это в виду, мы внесли в GHC простое изменение: вместо сборки GHC в виде монолитной программы, мы собираем GHC в виде библиотеки, которая компонуется затем с небольшим модулем Main, позволяющим сделать компилятор GHC исполняемым файлом, но которая также поставляется и как библиотека, так что пользователи могут вызывать компилятор из своих собственных программ. Одновременно мы собираем интерфейс API, с помощью которого функциональные возможности компилятора GHC становятся доступными для клиентских модулей. Интерфейс API предоставляет достаточно много функций с тем, чтобы можно было реализовать пакетный компилятор GHC и интерактивную среду GHCi, он позволяет получить доступ к отдельным проходам компиляции, например, синтаксическому анализу и к проверке типов, а также дает возможность выполнять проверку структур данных, создаваемых с помощью этих проходов. Такое изменение позволило разработать широкий спектр инструментальных средств, созданных с применение интерфейса API компилятора GHC, в том числе:

  • Инструмент документирования, Haddock, который читает исходный код на языке Haskell и создает документацию на языке HTML.
  • Новые версии интерактивной среды доступа GHCi с дополнительными функциями, например, функцией ghci-haskeline, которую впоследствии снова вернули в GHC.
  • Различные варианты среды разработки IDE, в которых предлагаются расширенные возможности навигации по исходному коду языка Haskell, например, Leksah.
  • Простой вариант API - hint, с помощью которого можно на лету оценивать исходный код на языке Haskell.

Система пакетов

Система пакетов была в последние годы ключевым фактором роста использования языка Haskell. Ее основное назначение заключается в предоставлении программистам, использующим язык Haskell, возможности пользоваться кодом, созданным другими, поскольку это важный аспект расширяемости: система пакетов применяется в совместно используемом коде самого компилятора GHC и за его пределами.

В системе пакетов реализованы различные фрагменты общей инфраструктуры, которые вместе позволяют достаточно просто пользоваться общим кодом. Благодаря наличию системы пакетов, само сообщество создало очень много совместно используемого кода; вместо того, чтобы полагаться на библиотеки, получаемые из одного источника, программисты, использующие язык Haskell, пользуются библиотеками, созданными всем сообществом. Такая модель хорошо зарекомендовала себя для других языков; например, система CPAN для Perl, хотя то, что язык Haskell является преимущественно компилируемым, а не интерпретируемым языком, ведет к несколько другому набору проблем.

В своей основе система пакетов позволяет пользователю управлять библиотеками кода Haskell, написанными другими людьми, и использовать их в своих собственных программах и библиотеках. Установка библиотеки Haskell выполняется просто с помощью всего одной команды, например, команда:

$ cabal install zlib

загружает код пакета zlib с сайта http://hackage.haskell.org, компилирует его с помощью компилятора GHC, устанавливает скомпилированный код где-нибудь в вашей системе (например, в вашем домашнем каталоге в системе Unix), и с помощью компилятора GHC регистрирует установленный пакет. Кроме того, если пакет zlib зависит от других пакетов, которые еще не установлены, то перед компиляцией самого пакета zlib они также будут загружены, скомпилированы и установлены. Это чрезвычайно удобный способ работы с совместно используемыми библиотеками кода на языке Haskell.

Пакет система состоит из четырех компонентов, причем только первый из них собственно является частью проекта GHC:

  • Инструментальные средства для управления базой данных пакетов, которая является просто хранилищем информации о пакетах, установленных в вашей системе. Компилятор GHC при запуске читает базу данных пакетов, поэтому ему известно, какие пакеты доступны и где их найти.
  • Библиотека, называющаяся Cabal (Common Architecture for Building Applications and Libraries - Общая архитектура для сборки приложений и библиотек), в которой реализованы функции сборки, установки и регистрации отдельных пакетов.
  • Сайт http://hackage.haskell.org, на котором хранятся пакеты, создаваемые и загружаемые на сайт пользователями. Сайт автоматически собирает документацию для пакетов; документацию можно просматривать в режиме online. На момент написания данной статьи, на сайте Hackage было размещено более 3000 пакетов самого различного функционального назначения, в том числе библиотеки баз данных, веб-фреймворки, инструментальные средства для создания графических интерфейсов, программы для различных структур данных и работы с сетями.
  • Инструментальное средство cabal, который связывает воедино сайт Hackage и библиотеку Cabal: с его помощью осуществляется скачивание пакетов с сайта Hackage в правильной последовательности, разрешаются зависимости пакетов и выполняется их сборка, а затем пакеты устанавливаются. На сайт Hackage можно также загружать новые пакеты с помощью команды cabal, выполняемой из командной строки.

Эти компоненты разрабатывались в течение нескольких лет членами сообщества Haskell и командой разработчиков компилятора GHC, и теперь эти компоненты вместе образуют систему, которая прекрасно вписывается в модель разработки с использованием открытого исходного кода. Нет никаких барьеров, которые бы мешали совместно использовать код, а также тот код, которые другие разработчики объявляют как совместно используемый (конечно при условии, что вы соблюдаете соответствующие лицензии). Вы можете использовать пакет, который кто-то написал, буквально через несколько секунд после того, как найдете его на сайте Hackage.

Сайт Hackage оказался настолько успешным, что среди оставшихся проблем у него теперь те, что связанны с масштабированием: например, пользователи считают, что трудно делать выбор одного из четырех различных фреймворков баз данных. Текущие разработки направлены на решение этих проблем так, чтобы это устроило сообщество. Например, возможность, разрешающая пользователям комментировать пакеты и назначать им баллы, позволит облегчить поиск лучших и самых популярных пакетов, а возможность, позволяющая собирать от пользователей данные об успешной или неудачной сборке пакетов и получать отчеты, поможет им избегать использовать те пакеты, у которых нет сопровождения или в которых есть проблемы.


Продолжение статьи: Система времени выполнения