В основу Рефала положены такие операции, как сопоставление аргумента с образцом и подстановка известных значений в шаблон. Структурами данных, в отличие от подавляющего большинства динамически типизированных функциональных языков, являются не списки, а более общие структуры данных — объектные выражения. В отличие от списков, которые можно обрабатывать только в одном направлении, объектные выражения можно анализировать с начала, с конца, добавлять данные в начало и в конец, а также строить из них деревья, используя вложенные конструкции.
Помимо базовых операций сопоставления с образцом и подстановки, многочисленные диалекты языка предлагают различные расширения, например, Рефал 2 предлагает более выразительные средства сопоставления с образцом, Рефал 5 — условия и блоки, Рефал Плюс обобщает идеи Рефала 5 относительно блоков и условий, вводя такую категорию, как действия, Рефал 6 систематизирует идеи Рефала Плюс, нововведением Рефала 7 являются вложенные функции, Динамический Рефал расширяет идеи Рефала 5 и Рефала 2.
Модульный Рефал
Модульный Рефал — диалект языка функционального программирования Рефал, разрабатываемый мной (А. В. Коноваловым aka Маздайщиком) в течение последних нескольких лет. Краткую историю разработки можно почитать здесь.
Можно отметить следующие ключевые особенности диалекта Модульного Рефала.
- Синтаксис похож на синтаксис Рефала 5.
- Модули в стиле Модулы-2 или Оберона-2 (эта особенность вынесена в название языка). Модуль предоставляет те или иные ресурсы в виде экспортируемых функций (в перспективе не только функций), чтобы использовать ресурсы модуля, его надо импортировать.
- Модули могут группироваться в пакеты (практически калька с языка Java).
- Термы абстрактных типов данных — особые скобочные термы, доступ к содержимому которых возможен только в том модуле, в котором они объявлены.
- Ограниченная поддержка функций высших порядков — указатели возможны пока только на глобальные функции. Библиотечный модуль MLambda в некоторой степени нивелирует этот недостаток.
- Управляющие конструкции (пока) ограничены только подмножеством Базисного Рефала, то есть предложения имеют вид образец = результат;.
- Наличие глобальных переменных, так называемых статических ящиков (название взято из диалекта Рефал 2, там есть ещё и динамические ящики). Отсутствие копилки (её можно эмулировать применением статических ящиков, поэтому она не нужна).
- Отсутствие встроенных в язык функций. Функции, которые можно вызвать в коде, должны быть или объявлены в текущем модуле, или импортироваться из других модулей. Соответственно, функции, невыразимые на Рефале (ввод-вывод, арифметика), реализованы в библиотечных модулях, которые не имеют исходных текстов и реализуются на целевом языке.
- Реализация представляет собой самоприменимый компилятор (т. е. компилятор написан на Модульном Рефале).
- Архитектура поддерживает несколько front-end’ов и back-end’ов. На данный момент реализован только front-end Модульного Рефала, 5 back-end’ов, из которых два служебных (генерация интерфейсов модулей и построение таблицы перекрёстных связей), три других генерируют код на целевом языке:
- генерация непосредственно в C++,
- генерация в Простой Рефал (это тоже мой диалект, см. ниже),
- генерация в Рефал 5 (исторически самый первый, является устаревшим, но жалко выкинуть).
- Относительно удобный интерфейс с языком C++.
- Отсутствие оптимизаций. :-(
- Работает только на платформе Windows.
Простой Рефал
Когда я начинал работать над Модульным Рефалом, у меня не было чёткого представления о том, как преобразовывать код на Рефале в императивный код (после рассмотрения синтаксиса и семантики будет понятно, почему эта задача нетривиальна). Для выяснения этого вопроса было решено разработать транслятор упрощённого подмножества Рефала в императивный код, в качестве которого был выбран C++.
Со свойственным мне размахом, я расширил задачу и стал писать компилятор Рефала в C++, максимально упрощая второстепенные сложности (например, задачи оптимизации полученного кода, модульность, произвольный порядок функций в файле исходного текста — требуется, чтобы перед использованием функция была объявлена или определена). В результате получился диалект Простого Рефала.
Поскольку в данном случае первичными были требования к реализации (максимально простая, компиляция в C++), то особенности языка и реализации я перечислю в общем списке:
- Наиболее простая компиляция в C++, и как следствие:
- каждый файл исходного текста Простого Рефала при компиляции превращается в файл исходного текста на C++,
- как следствие, модульность в стиле языка C: функции, объявленные экспортируемыми с ключевым словом $ENTRY (без модификатора static), объявляются в других единицах трансляции как $EXTERN (соответственно, extern),
- однопроходная компиляция: отдельные элементы (предобъявления функций, определения функций, даже отдельные предложения) компилируются в соответствующие конструкции целевого языка (соответственно, объявления и определения функций),
- если вызываемая функция находится после вызывающей, необходимо её предобъявление,
- подмножество только Базисного Рефала,
- стандарт C++98 требует, чтобы функция с модификатором static объявлялась тоже с модификатором static, поэтому чтобы различать предъобъявления локальных функций и функций, объявленных как $ENTRY, используются, соответственно, ключевые слова $FORWARD и $EXTERN.
- Типы данных: структурные скобки, символы (characters), целые числа ограниченного диапазона (0…2³² − 1) и имена функций (указатели на функции).
- Поскольку идентификаторов в языке нет, а потребность в них имеется, существуют синтаксические средства для определения «пустых» функций: ключевые слова $ENUM и $EENUM (entry enum или export enum). Такие функции в дальнейшем используются вместо идентификаторов.
- Отсутствие оптимизаций.
- Поскольку реализация использует только стандартную библиотеку C++, компилятор может работать как на платформе Windows, так и на платформах POSIX.
- статические ящики, объявляются ключевыми словами $SWAP и $ESWAP (второе, разумеется, entry swap),
- идентификаторы, объявляются ключевым словом $LABEL, при их использовании имя следует предварять символом # (например, #True).
- оптимизация построения результатных выражений путём замены прямой кодогенерации на интерпретацию. Выполнялось совместно с Сухаревым Вадимом aka XpycT;
- использование безымянных вложенных функций в стиле Рефала 7,
- написание компилятора Простого Рефала в C#. Выполнялось совместно с Быкадоровой Натальей,
- оптимизация построения результатных выражений путём минимизации числа преобразований поля зрения. Выполнялось совместно с Дорофеевым Михаилом aka Patriot.
В данном руководстве описываются языки программирования Модульный и Простой Рефал, а также их реализации. Руководство не является учебником по программированию на Рефале, в нём не приводятся различные идиомы и приёмы программирования. О том, как программировать на Рефале, можно почитать отдельную работу «Идиомы программирования на Рефале».
Благодарности
Хочу поблагодарить Сухарева Вадима aka XpycT, Быкадорову Наталью и Дорофеева Михаила aka Patriot за помощь в проведении исследований, и в особенности преподавателя кафедры ИУ9 МГТУ имени Н. Э. Баумана Скоробогатова Сергея Юрьевича, разработчика Рефала 7, за ценные советы и ценную критику.