Back ] Up ] Next ]

РЕФАЛ-6

ОБРАБОТКА НЕУСПЕХОВ

До сих пор (то есть в Рефале-5) неуспехи встречались только внутри функции и там же перехватывались. Поскольку блок может выработать неуспех, а тело функции есть блок, то и функция может иметь результатом неуспех. Более того, мы теперь считаем, что нормальная функция всегда вырабатывает либо нормальный результат, либо неуспех. Содержательно неуспех означает, что функция на данном значении аргумента не определена.

В отличие от Рефала-5 в Рефале-6 блоки прозрачны для неуспехов. То есть, если все альтернативы блока окончились неуспехом, то и блок завершается неуспехом. (В Рефале-5 в этом случае фиксируется аварийная ситуация.)

Таким образом, вызов функции может породить неуспех. Результатное выражение вырабатывает неуспех, когда хотя бы один вызов функции в нем вырабатывает неуспех.

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

Введем понятие действия как обобщение понятий результатного и образцового выражений. Всякое действие выполняется в среде, где имеется некоторый набор переменных со значениями и некоторое отдельное выходное значение. Все значения суть объектные выражения. Всякое действие может как пополнить среду новыми переменными, так и изменить ее выходное значение.

Образец использует выходное значение среды и может пополнить среду новыми переменными, но выходное значение среды не изменяет. Результатное выражение всегда изменяет выходное значение на новое, но не изменяет списка переменных; оно всегда игнорирует прежнее выходное значение.

Ничто не мешает считать действием также и блок. Причем образцовый блок использует прежнее выходное значение среды, а результатный его игнорирует. Блок никогда не изменяет списка переменных. Выходное значение блока есть значение его успешно завершившегося окончания. Напомним, что блок есть последовательность окончаний.

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

Синтаксически образцовое и результатное выражение различаются контекстом. Образцовому выражению предшествует знак ":" или "::", либо начало тела функции. Результатному выражению предшествует знак "," или "=". Скобки "{" и "}" сохраняют контекст, передавая его внутрь на начало каждого окончания. После каждого действия, если оно не последнее в окончании, контекст должен быть указан явно одним из упомянутых знаков.

Разница между знаками ":" и "::", а также между "," и "=" проявляется в ситуациях, когда в следующем за ними окончании возник неуспех. Знаки ":" и "," прозрачны для неуспехов, т.е. неуспех передается сквозь них справа налево без проблем. Знак "=" запрещает неуспех во всем следующем за ним окончании. Если он все же возникает, то работа программы аварийно прекращается с диагностикой "Unexpected fail" ("Неожидаемый неуспех"). Знак "::" аналогично запрещает неуспех, но не во всем последующем окончании, а только в непосредственно следующем образце или образцовом блоке. Кроме того, знак "::" запрещает перехват неуспеха следующим за ним образцом с открытыми переменными (т.е. требует, чтобы образец с открытыми переменными отработал сразу до первого успешного варианта сопоставления и более не пересопоставлялся).

Перед всяким действием может быть вставлен знак "#" - отрицание, который инвертирует результат действия: нормальный результат заменяется на неуспех, а неуспех на пустое выражение.

Для гибкого управления неуспехами вводятся знаки "/" и "\", которые могут ставиться между действиями.

Знак "/" усиливает неуспех, проходящий через него справа налево так, что этот усиленный неуспех не может быть перехвачен предшествующим образцом или очередным окончанием объемлющего блока. Таким образом, неуспех выходит на уровень вызова функции, где и теряет свою избыточную силу.

Знак "\" ослабляет усиленный неуспех, проходящий через него справа налево, после чего он может быть перехвачен обычным образом. Неуспех может быть многократно усилен при помощи нескольких знаков "/". В этом случае до полного ослабления он должен пересечь столько же знаков "\".

Рассмотрим действие знаков "/" и "\" в случае простого окончания, состоящего из участков A1 - A5 (каждый участок - последовательность действий):

     A1 \ A2 \ A3 / A4 / A5

Неуспех в A5 может быть перехвачен только на участке A1, неуспех в A4 - на участках A2 и A1, но не в A3. Можно считать, что знаки "/" и "\", стоящие вдоль одного пути как бы образуют скобочную структуру: неуспех передается от правой скобки "/" сразу к соответствующей левой скобке "\". Чтобы не перепутать знаки "/" и "\" следует представлять себе, что усиленный неуспех проходит как бы снизу. 

Рассмотрим на примерах работу описанных правил.

1. Обычное предложение базисного Рефала:

     EP = ER;

С точки зрения новых понятий это окончание, состоящее из двух действий: образца EP и результатного выражения ER. Перед ним стоит знак "=", поэтому всякий неуспех при вычислении ER приведет к аварийному завершению программы. При этом будет выдано сообщение "Unexpected fail", и можно будет увидеть тот вызов функции из ER, который выдал неуспех. Таким образом, если вся программа написана на Базисном рефале, то всякий неуспех всякой функции немедленно приводит к аварии.

Для того, чтобы позволить неуспеху в ER быть перехваченным образцом EP или следующим предложением, это предложение следует записать в виде:

     EP, ER;

2. Предложение с условием:

     EP, ER1 : EP1 = ER;

Здесь имеется последовательность из 4-х действий. Образец EP1 сопоставляется с результатом вычисления ER1. Неуспех в ER приводит к аварии, неуспехи в остальных местах перехватываются либо в образце EP (открытыми переменными), либо следующим предложением. Если "условие" заведомо выполняется, то это можно указать одним из двух способов:

     EP = ER1 : EP1 = ER; 

или

     EP, ER1 :: EP1 = ER;

Первый способ более радикален, поскольку он запрещает также откаты из ER1.

3. Рассмотрим условие, у которого левая часть есть вызов некоторой функции F:

     ..., <F EA> : EP1 =... 

Пусть функция F имеет определение

     F BLK;

Предположим, что блок BLK не содержит переменных, одноименных с переменными среды, в которой исполняется вызов <F EA>. Тогда этот вызов может быть текстуально замещен парой EA:BLK :

     ..., EA : BLK : EP1 =...

Такая замена возможна всегда, когда вызов <F EA> не является вложенным в другое результатное выражение.

4. Сложное результатное выражение может быть эквивалентно разложено на простые действия, среди которых результатные выражения либо не содержат вызовов функций (пассивны), либо являются вызовом функции от пассивного аргумента. Пусть EL <F EA> ER - результатное выражение, причем выделенный вызов является ведущим в нем (т.е. самым левым из самых внутренних), и пусть eX - переменная, не встречающаяся в теле охватывающей функции. Тогда это действие можно заменить на последовательность действий:

     <F EA> : eX , EL eX ER

5. Рассмотрим образцовое окончание:

     (A s1 e2) = (A s1 e2);

Здесь пришлось повторить целиком образец, чтобы сказать, что ничего изменять не нужно. Теперь это можно переписать так:

     eA : (A s1 e2) = eA;

6. Условие, что символ sX не есть A можно записать так:

     ..., sX : # A ...

7. А так можно записать условие, что символ sX есть либо A, либо B:

     ..., sX : { A;B }, ...

8. Часто бывает нужно проигнорировать результат вычисления некоторой функции. В этом случае надо просто поставить после вызова функции знак "," или "=":

     EP = <F EA> =;

9. Пустой блок { } равносилен безусловному неуспеху.

МОДУЛЬНОСТЬ

Программа есть совокупность определений именованных объектов. Функция - это частный случай объекта. Если имеется объект, поименованный именем A, то символ *A есть ссылка на этот объект. (Для удобства написания вызовов функций запись <A ...> есть сокращение для <*A ...>). Для ограничения текстуальной области действия имен объектов служит понятие модуля.

Модулем является часть программы, ограниченная ключевыми словами $MODULE и $END. Остаток строки после них считается комментарием, поэтому их рекомендуется писать на отдельных строках и снабжать одинаковыми метками.

Между этими директивами расположены определения объектов данного модуля. По умолчанию область действия их имен ограничена модулем. Но чтобы сделать имя доступным вне модуля, его нужно объявить в модуле внешним. Это делается директивой $EXTERN вида:

     $EXTERN A,B,C;

которая помещается внутри модуля в любом месте. Сама директива $EXTERN не определяет объект, а лишь делает его доступным вне или внутри модуля, в зависимости от того, определен ли он внутри или вне модуля. Таким образом, эта директива обеспечивает как экспорт, так и импорт имен объектов.

Если вы забудете импортировать объект извне, то это будет сделано автоматически с выдачей предупреждения.

Функцию можно также экспортировать путем приписывания ключевого слова $ENTRY перед ее определением, например:

 $ENTRY GO = <Main>;

Модули могут вкладываться друг в друга. Внутренний модуль с позиции внешнего равносилен совокупности определений своих экспортированных объектов.

Разбиение программы на файлы, вообще говоря, не диктует соответствующего разбиения на модули. Файл как единица компиляции и/или загрузки может содержать как несколько "открытых" определений объектов, так и несколько модулей. Загружая файл, вы пополняете глобальную среду программы, определенными в этом файле именами объектов. При загрузке модуля в глобальную среду попадают только внешние имена.

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

Для совместимости с ранними версиями Рефала, где модулем служил файл, и не было явных директив описания модуля, введено следующее правило умолчания: если на верхнем уровне вложенности модулей имеется директива экспортирования определенного в файле объекта, то автоматически в начале и конце файла приписываются директивы $MODULE и $END. Это будет сделано, например, если хотя бы перед одной из функций файла стоит ключевое слово $ENTRY.

Хотя файл и не обязательно создает модуль, но импорт внутрь файла следует делать явно, иначе выдается предупреждение.

Заметим, что механизм модульности "неизвестен" основной интерпретирующей программе ri.exe, которая "понимает" лишь одноуровневый поток выражений и определений объектов. При этом существенно, что глобальная среда *SYSTABLE доступна как объект (типа TABLE) языка Рефал. Весь механизм модульности определен на Рефале, частично в компиляторе, частично - в функции LOAD, определенной в библиотечном вступлении i.rex. 

ДИРЕКТИВА ОПРЕДЕЛЕНИЯ ОБЪЕКТА

Помимо определений функций во входном языке имеется общий класс директив определения объектов. Они имеют вид:

     $KeyWord name1 value1, name2 value2,...;

$KeyWord определяет тип объекта, name - имя, value - начальное значение. Последнее должно быть выражением Рефала без переменных (но вызовы функций могут содержаться, при условии, что эти функции определены выше или ранее загружены).

Способ занесения начального значения зависит от типа. Имеются несколько стандартных типов, и кроме того, пользователь может создать свой тип $MyType, определив ранее функцию MyType, которая принимает аргумент вида s.Ref e.Value, где s.Ref - символ-ссылка на объект, а e.Value - начальное значение, и выдает s.Ref. Предполагается, что данная функция инициализирует объект s.Ref с учетом значения e.Value.

Имеются следующие стандартные типы: $BOX, $VECTOR, $STRING, $TABLE, $CHANNEL, $MASK (они же целые), $REAL. Среди них начальные значения e.Value могут иметь только так называемые контейнеры: $BOX, $VECTOR, $STRING, $TABLE, $MASK. Это начальное значение заносится после создания пустого объекта соответствующего типа s.Ref при помощи вызова функции

     <SETV s.Ref e.Value>
Для инициализации числовых объектов удобно пользоваться в качестве ключа встроенной функцией SET, например:
     $SET N 5, PI 3.14159;

Особая директива $EXEC того же формата служит для вызова произвольной функции F от аргумента ARG:

     $EXEC F1 arg1, F2 arg2, ...;

Вызов производится при загрузке файла в момент обработки этой директивы. Эта директива равносильна вычислению выражения

     <F1 arg1> <F2 arg2> ...

ВЫЗОВ ОБЪЕКТА

Рефал позволяет записать ссылку на функцию F как символ *F и передать его как элемент данных другой функции. Та может воспользоваться им для неявного вызова функции F в виде: <sf ea>, или даже просто <ea>. Такая запись в принципе не исключает возможность того, что в качестве имени функции попадет произвольный объект - не функция. В этом случае рефал-система заменит вызов <ea> на вызов <APPLY_OBJECT ea>. Функцию APPLY_OBJECT пользователь может определить самостоятельно (на верхнем уровне модульности). По умолчанию будет использован следующий стандартный вариант (его определение - в файле i.rex):

* This function is invoked by <tR eA> when tR is not a function
APPLY_OBJECT  tR eA , {
     <GETV tR> : {
           Object tB eF , / <tB tR eF eA>;
           tB eF , / <tB eF eA>;
           , <PRINTLN! *STDERR "Function " tR " is undefined">,
                / {};
           };
     <SEND tR eA>;
     };
MakeObject t.Ref t.BaseObj e.FixArg =
            <SETV <MAKE CHAIN t.Ref> Object t.BaseObj e.FixArg>;
Это определение обеспечивает следующие три возможности:

1. Если вызов имеет вид <t.Ref e.Arg>, где t.Ref - объект, построенный вызовом <MakeObject t.Ref t.BaseObj e.FixArg>, то он будет заменен на

<t.BaseObj t.Ref e.FixArg e.Arg>. Этим обеспечивается объектно-ориентированное программирование. Функция MakeObject играет роль универсального конструктора объекта, результатом ее является сам объект t.Ref. t.BaseObj (функция или объект) - обработчик сообщений для объектов данного класса, он определяет класс объекта; e.FixArg - собственные данные объекта, которые приписываются к каждому сообщению перед вызовом обработчика. e.Arg - сообщение, посылаемое объекту t.Ref. Обратите внимание, что обработчику передается также ссылка на исходный объект t.Ref. Этим создается основа для поддержания множественного наследования.

2. Вызов вида <(eA) eB> заменяется на <eA eB>. Это позволяет рассматривать терм (F parg), где parg - начальная часть аргумента функции F как результат специализации функции F по фиксированной части аргумента parg, например (*АDD 1) - функция прибавления единицы.

3. Для остальных объектов t.Ref, делается попытка заменить <t.Ref e.Arg> на <SEND t.Ref e.Arg>. SEND - это встроенная функция, которую "понимают" некоторые встроенные объекты.

Обратите внимание на использование усилителей неуспехов "/". Это важно для корректности аварийной диагностики. Изменяя тело функции APPLY_OBJECT, искусный пользователь может определить свои способы вызова объектов - не функций.

ДРУГИЕ РАСШИРЕНИЯ ЯЗЫКА

V-переменные

В рефал-6 включены v-переменные, которые могут принимать в качестве значения любое непустое значение. Такая переменная vi равносильна комбинации двух переменных ti ei (если, конечно, проигнорировать тот факт, что в языке запрещены одинаковые индексы у переменных разных типов).