Главная цель новой реализации Рефала -- исправить основной недостаток существующих: старые системы были в основном предназначены для создания замкнутых программ на Рефале, вызываемых как готовое приложение с командной строки. Средства интеграции с другими языками были развиты слабо. Лишь одна из систем -- Рефал-2 (переживавшая 3 "инкарнации") имела достаточно хорошо документированный интерфейс с Фортраном (на БЭСМ-6), PL/I (на IBM/360) или С (на IBM/PC). Однако, тот "низкоуровневый" стиль интерфейса не удовлетворяет требований сегодняшнего дня: он был рассчитан на то, что программа на языке "низкого уровня" подстраивается под понятия реализации Рефала "ради эффективности" последнего. Теперь тех требований "эффективности" нет. Современный стиль: "все во имя человека", то есть используем мощность компьютеров для создания удобств разработчикам, для удешевления их труда.
В современном мире доминируют объектно-ориентированные языки. Именно с ними и надо интегрироваться. Рефал должен стать инструментом программирования отдельных ("интеллектуальных") подсистем в рамках больших проектов, ведущихся на Яве, C# и т.п.
Главным фактором, определяющим выбор решений, должен стать следующий:
Как можно более "прозрачная", "бесшовная" ("seamless") интеграция с языком Java. Это означает, что описание интерфейса Рефал-Java должно быть как можно более простым, чтобы программистам было легко вызвать функцию на Рефале из программы на Яве и наоборот.
Второе по важности требование проекта:
Основная часть реализации Рефала, после которого ею может пользоваться квалифицированный программист на Рефале и Яве, должна быть сделана быстро (за один-два месяца). После этого система будет развиваться дальше.
Стороны отдают себе отчет, что в результате первого этапа полноценная система еще не появится и потребуются дальнейшие вложения средств и сил. Было бы очень выгодным организовать развитие системы в режиме open-source. Но это также требует определенных организационных усилий.
См. подробнее в разделе "Состав Рефал-системы".
Выбор языка для разработки определенного класса приложений -- серьезное бизнес-решение, с анализом преимуществ и недостатков. Безусловного и идеального решения здесь нет. Проблема выбора Рефала усложняется тем, что подходящей стабильной реализации, поддерживаемой крупной софтверной фирмой нет, и нужно изготавливать свою систему. Перечислим факторы, которые, на наш взгляд, надо учитывать.
В 70-е годы Рефал был уникален в своем классе. Ближайшим родственником был Лисп, который по сравнению с Рефалом -- язык низкого уровня (к нему неплохо подходит метафора "рекурсивный язык ассемблера"). Любители низкого уровня выбирали Лисп (а в 80-е годы его потомка -- язык Scheme), а те, кто предпочитал думать, а не кодировать, -- Рефал. Родственные высокоуровневые языки (макро-расширения Лиспа, Snobol и др.) заметно отставали от Рефала по качеству.
В 80-е годы ситуация изменилась. Появилась плеяда функциональных языков высокого уровня: Hope, Miranda, ML (потом он SML, Standard ML), Caml, Haskell. Из этих языков наибольший интерес представляют SML, Caml, Haskel. Их общая черта -- статическая типизация, в то время как Лисп и Рефал были нетипизированными (статически), точнее: типизированными динамически, то есть типы данных хранились в их представлении и проверялись во время счета. Интересно, что новых нетипизированных функциональных языков не появилось.
Общими у этих языков являются такие свойства:
В некотором смысле родственниками Рефала можно также считать языки логического программирования -- Пролог и др., а также активно развивающиеся в последнее время функционально-логические. Однако, они, с нашей точки зрения, перегружены конкретными средствами решения обратной задачи, которые далеко не всегда нужны, а если нужны, то другие. Рефал не содержит подобных механизмов, однако по сравнению с упомянутыми функциональными языками в нем имеются средства организации перебора, включая отождествление по сложным образцам. Тем самым он выполняет роль "функционального языка, продвинутого в сторону языков логического программирования".
Таблица. Классификация функциональных языков по двум ортам: высокий / низкий уровень, типизированный / нетипизированный:
типизированный | нетипизированный | |
высокий уровень |
SML, Caml, Haskel и др. |
Рефал |
низкий уровень |
- | Lisp, Scheme |
Типизированные языки предпочтительнее для больших проектов, когда отладка становится особенно затруднительной и ошибки, вылавливаемые типизацией, заметно увеличивают скорость разработки и удешевляют ее. А в небольших или в хорошо структурированных проектах, когда каждую подсистему разумного размера можно разрабатывать и мыслить отдельно, более популярны нетипизированные или слабо-типизированные языки т.н. scripting languages: Visual Basic, JavaScript, Tcl (последний особенно интересен для сравнения с Рефалом) и др. Lisp и Scheme также выполняют роль scripting languages (достаточно вспомнить знаменитый редактор Emacs, расширяемый на Лиспе). Рефал также хорошо подходит на роль scripting language.
И наконец, важнейшим бизнес-фактором является выбор между "чужым" и "своим": между хорошо-отработанным "фирменным" языком и реализацией языка почти одновременно с его внедрением в тесном контакте разработчиков приложения и языка. Это самый рисковый аспект. Здесь можно учитывать такие принципы (в равной мере пригодные для разработки как software, так и hardware):
Интересно, что факторы "чужой / свой" коррелируют с "типизированный / нетипизированный". Реализации (особенно компиляторы) нетипизированных языков заметно проще, чем типизированных, а с учетом того, что можно воспользоваться такими развитыми платформами как Java или MS.Net, воплотить Рефал будет намного дешевле чем скажем, SML (особенно без учета полной интеграции в IDEs и богатых средств отладки, которые можно дорабатывать во вторую очередь).
Информация по Рефалу имеется на сайте:
(это зеркала одного и того же сайта) и списках рассылки refal@botik.ru и refal-plus@botik.ru, архивируемых здесь:
На настоящий момент имеются такие реализации Рефала на Intel-платформах:
За основу новой реализации Refal-on-Java будет взята система Рефал-6. Ее входной язык близок к Рефалу 5 и Рефалу Плюс, но с некоторыми отличиями. Ее компилятор написан на Рефале-6. Синтаксис языка на первом этапе предлагается менять минимально. На следующих этапах возможно развитие языка. Сразу понадобится добавить понятия модульности Явы (пакеты; именование функций составными именами через точку). Подробности см. ниже.
Языки, интегрированные в общую объектно-ориентированную среду, характеризуются следующими свойствами:
К новой реализации Рефала не предъявляется требований, чтобы он удовлетворял этим свойствам в полной мере. Взаимодействие программ на Рефале и на Java будет проходить через средства, которые лежат в "пересечении" их понятий:
Подробнее см. ниже в разделе "Технические решения".
В будущем возможно развитие Рефала в сторону все более полного языка-потребителя вплоть до вызова виртуальных методов в объектах.
Полномасштабная Рефал-система должна состоять из следующих подсистем:
Знаками (+), (+-), (-+), (–) помечены 4 уровня приоритетов. На 1-ой очереди делаются только (+), (+-), причем (+-) в "сыром" виде.
На первом этапе ключевым словом является "минимальный". Будут реализованы:
Решения, перечисленные ниже, -- это ориентировочный проект. Они могут меняться в некоторых деталях, но вряд ли изменятся по существу. Их фиксация произойдет только после того, как заработает реализация и начнется эксплуатация. Но даже после этого возможны изменения по взаимному решению разработчиков и заказчиков.
За основу берется синтаксис и семантика Рефала-6. Вносятся некоторые недостающие элементы Рефала Плюс. Делаются также некоторые расширения, продиктованные взаимодействием с языком Java.
Программа на Рефале состоит из модулей, каждый модуль содержит набор именованных сущностей, например, функций (а также, возможно, ящиков, векторов, таблиц, каналов и т.п.)
Модуль в Рефале описывается парой файлов: rfi-файл интерфейса и rfj-файл определений (как в Рефале Плюс). Файл интерфейса содержит объявления сущностей, доступных из других модулей.
Если интерфейсный файл отсутствует, то считается, что он содержит одно объявление:
$Func main e:String=;
Из других модулей сущности доступны по полному имени вида <имя-модуля>.<простое-имя-сущности>. Возможен также доступ по короткому имени <простое-имя-сущности>, если в теле использующего файла определений имеется предложение
$use <имя-модуля>...; -- как в Рефале Плюс
Имена модулей в свою очередь тоже могут быть составными, то есть иметь префикс, отделенный точкой. Префикс тоже может быть составным.
Предложение вида (может находится только в начале файла определений)
$import <префикс>.<простое-имя-модуля>;
делает возможным <простое-имя-модуля> в дальнейшем записывать без префикса. Аналогично, предложение
$import <префикс>.*;
дает возможность записывать простым образом все имена модулей, имеющие данный префикс.
Чтобы данный модуль имел данный префикс, надо в начале модуля написать:
$package <prefix>;
Префикс определяет путь в структуре каталогов от некоторого корня к каталогу, в котором лежат файлы интерфейса и определений данного модуля.
1-я очередь: Нет интерфейсных файлов и нет предложения $use, но для импорта есть предложение вида
$from <имя-модуля> $import <простое-имя-сущности>,...;
которое дает возможность использовать в теле модуля упомянутые имена сущностей (функций, статических символов-ссылок и т.п.) без указания имени модуля. Для экспорта используется предложение:
$export <простое-имя-сущности>...;
Принципы компиляции:
Предложения $import и $prefix переводятся один к одному (без знака $) в начало файла на java. Кроме того, добавляется предложение import refal;
Таким образом, пользователь может сам написать модуль на Java, снабдив его файлом интерфейса, и его статические методы и объекты будут доступны из Рефала. (В 1-й очереди интерфейсные сущности могут включать только статические методы сигнатурой (Object[]) -> Object[] и статические поля с объектом в качестве значения).
Каждая объявленная сущность модуля, кроме функций, отображается в одноименное статическое поле класса. Функции отображаются в одноименные статические методы.
Чтобы экспортировать функцию как объект, ее нужно дополнительно описать предложением
$Func <Name>;
тогда в других модулях доступна ссылка на эту функцию как символ:
*Name
-- как в Рефале-6
или
&Name
-- как в Рефале Плюс
(В 1-й очереди этот вид экспорта будет порождаться по умолчанию для всех функций, упомянутых в предложении $export).
Аналогичные ссылки на локальные функции модуля можно вводить без дополнительных объявлений.
Экспортируемые имена объявляются public, остальные - private.
Функция может иметь предобъявление вида:
$Func <name> <входной формат> = <выходной формат>;
или, в случае откатной функции:
$Func? <name> <входной формат> = <выходной формат>;
Если таковое отсутствует, то считается, что функция имеет объявление
$Func? <name> e=e;
(Внимание: здесь имеется отличие от Рефала Плюс, где по умолчанию функции безоткатные. Это связано с тем, что основу нашего языка составляет Рефал-6, где все функции откатные. Так удобнее при той семантике знака '=', которая имеет место в Рефале-6).
В 1-й очереди возможности описывать формат не будет, все функции будут считаться откатными с форматом e=e. Входной функцией считается функция
$Func? Main e=e;
Реализация
На основе формата вычисляется сигнатура метода на Java. Каждой переменной входного формата соответствует один параметр. Тип параметра определяется типом переменной и возможным дополнительным спецификатором, например:
Переменная формата | Тип параметра |
e | Object[] |
t |
Object |
s | Object |
e:SomeClass | SomeClass[] |
t:SomeClass | SomeClass |
s:int | int |
e:float | float[] |
Для выходного формата тип результата определяется так:
Если в интерфейсном файле для функции <name> имеется дополнительное объявление
$Func <name>;
или имя внутренней функции используется в качестве символа-ссылки, то дополнительно к методу порождается одноименное поле
public/private refal.Function <name> =
new
refal.Function("<full-name>") {
Object[] eval(Object[] e) {
return <name>(e);
}
}
Если функция имеет объявление формата отличное от
$Func? <name> e=e;
то в тело виртуального метода eval вставляются дополнительные операторы преобразования формата, например:
$Func foo s:int: e = s:int;
public refal.Function <name> =
new
refal.Function("module.foo") {
Object[] eval(Object[] e) {
Integer s1 = (Integer) e[0];
Object[] e2 = new Object[e.length-1];
System.arraycopy(e,1,e2,0,e.length-1);
return new Object[] {new Integer(foo(s1,e2))};
}
}
1-я очередь: если в модуле определена функция Main, то автоматически добавляется метод:
public static void main(String[] args) { Main(args); }
Таким образом функция Main принимает выражение, термами которого являются последовательные параметры командной строки, каждый параметр - слово.
Выражение есть последовательность термов. Терм есть либо выражение в скобках, либо символ. Символ есть: литера, число, слово, символ-ссылка. Еще теперь появляется символ null.
Статические символы-ссылки описываются предложением вида
$<тип-ссылки> <символ>,... ;
Например:
$BOX A,B,C;
Реализация
Выражение отображается на массив типа java.lang.Object[]. Произвольный терм - на java.lang.Object. Выражение в скобках - опять-таки массив объектов. Неоднозначность снимается правилом: на верхнем уровне скобки снимаются. Надеюсь, не запутаемся.
Литеры представляются объектами типа java.lang.Character.
Слова - java.lang.String.
Числа - Integer, Long, Float, Double, BigInteger, BigDecimal. Запись констант можно сделать как в java (с L на конце - Long, иначе Integer, с f - Float, иначе - Double), для перевода в BigInteger, BigDecimal используется явная функция. Встроенные операции ADD, SUB, MUL, DIV, REM применимы равно к любым видам чисел.
В качестве символов-ссылок могут использоваться любые java-объекты (не массивы).
Тип ссылки - это класс на java. Для каждого символа компилятор создает одноименное статическое поле указанного типа и инициализирует его через конструктор без параметров, который в этом классе должен быть.
Встроенные классы BOX, ТАBLE ... находятся в пакете refal. Поэтому их тип может записываться всегда без префикса.
Возмем такой код на рефале:
Файл Sequence.rfj:
$from STDIO $import PRINTLN
PurgeEqual {
e1 e2, e2: $r e3 e3 e4 = <PurgeEqual e1 e3>;
e1 = e1;
}
Main e1 = <PRINTLN <PurgeEqual e1>>;
А вот как он будет странслирован в java:
Файл Sequence.java:
public class Sequence { // PurgeEqual { // e1 e2, e2: $r v3 v3 e4 = <PurgeEqual e1 e4>; // e1 = e1; // } static Object[] PurgeEqual (Object[] e0) { int len0 = e0.length; for (int i1=0; i1<=len0; i1++) { int len2 = len0-i1; for(int i3 = len2/2; i3>0; i3--) { if (Lang.exprsEqual(e0,i1,e0,i1+i3,i3)) { int i4 = 2*i3; int len4 = len2 - i4; int len5 = i1 + len4; Object[] e5 = new Object[len5]; System.arraycopy(e0,0,e5,0,i1); System.arraycopy(e0,i1+i4,e5,i1,len4); Object[] e6 = PurgeEqual(e5); return e6; } } } return e0; } // Main e1 = <PRINTLN <PurgeEqual e1>>; static private Object[] Main(Object[] e1) { Object[] e2 = PurgeEqual(e1); return STDIO.PRINTLN(e2); } public static void main(String[] args) { Main(args); } }