Добрый день всем!
Мне в личке справедливо пожаловались на «многабукаф — ниасилил», поэтому даю 
конспект письма про прогонку и неуспехи.
В первой части письма я кратко написал что такое прогонка, как её определил 
Турчин, и разобрал прогонку на примере. На этом примере было показано, что 
прогонка по Турчину для ограниченного Рефала (подмножество базисного Рефала) 
может расширять область определения программы.
Во второй части письма я предложил другое подмножество Рефала — ограниченный 
Рефал-6, в котором прогонка по Турчину уже не расширяет область определения 
программы. Ограниченный Рефал-6 является базисным подмножеством Рефала-6, в 
котором разрешены только L-образцы и, что главное, вместо равенства части 
предложения разделяются запятой.
В третьей части письма я спекулирую на тему того, что суперкомпилятор 
ограниченного Рефала расширяет область определения программы и предлагаю 
несколько способов это расширение обуздать.
 
С уважением,
Александр Коновалов 
 
From: Александр Коновалов a.v.konovalov87_AT_mail.ru <refal@botik.ru> 
Sent: Wednesday, December 18, 2019 1:13 AM
To: refal@botik.ru
Subject: Прогонка по Турчину и неуспехи
 
Доброй ночи всем!
Пришло в голову одно интересное соображение. Но сначала контекст.
В 1974 году Турчин описал прогонку как эквивалентное преобразование программы 
на Рефале (Москва: ЦНИПИАСС, 1974 ( 
<https://pat.keldysh.ru/~roman/doc/Turchin/1974-Turchin--E'kvivalentnye_preobrazovaniya_programm_na_Refale--CNIPIASS--ru.pdf>
 pdf)). На самом деле прогонка была описана на два года раньше (Киев-Алушта, 
1972 ( 
<https://pat.keldysh.ru/~roman/doc/Turchin/1972-Turchin--E'kvivalentnye_preobrazovaniya_rekursivnyx_funkcij__opisannyx_na_yazyke_Refal--ru.pdf>
 pdf)), но там термина «прогонка» ещё не было.
В этих работах описывается подмножество базисного Рефала — ограниченный Рефал, 
в котором в образцах запрещены любые t-переменные и открытые и повторные 
e-переменные. Для этого подмножества определена прогонка — замена одного из 
предложений функции F, содержащей вызов функции G на эквивалентный набор 
предложений (в том числе и пустой), в которых вызов G отсутствует. При этом 
семантика функции F на исходной области определения не меняется. Однако, 
область определения функции F при этом может расшириться — если ранее программа 
вылетала с ошибкой невозможности отождествления, то после прогонки одного из 
вызовов функции программа может перестать падать (вместо чего выдавать какой-то 
результат или зацикливаться).
Алгоритм прогонки я здесь описывать не буду, поскольку он есть в статьях по 
гиперссылкам выше. Лучше читать работу Киев-Алушта, 1972 ( 
<https://pat.keldysh.ru/~roman/doc/Turchin/1972-Turchin--E'kvivalentnye_preobrazovaniya_rekursivnyx_funkcij__opisannyx_na_yazyke_Refal--ru.pdf>
 pdf), поскольку она короче. Прогонка в ней — правило EF1.
Покажу на примере, как прогонка расширяет область определения. Пример простой, 
но расписан подробно, читать рекомендуется по диагонали.
Буду использовать синтаксис Рефала-5, поскольку мне он более привычен.
F {
  A e.Z = 1 e.Z;
  s.X e.Z = <G s.X> e.Z;
  t.X e.Z = 100 e.Z;
}

G {
  P = 10;
  Q = 20;
}
При прогонке мы комбинируем левые части предложений прогоняемой функции G с 
левой частью предложения, содержащего вызов прогоняемой функции. Сам вызов 
прогоняемой функции заменяем на соответствующие правые части. В результате 
получится такая функция F:
F {
  A e.Z = 1 e.Z;
  P e.Z = 10 e.Z;
  Q e.Z = 20 e.Z;
  t.X e.Z = 100 e.Z;
}
Но, можно заметить, что область определения расширилась. Если ранее вызов <F X 
Y Z> падал:
<F X Y Z> → <G X> Y Z → ошибка
то теперь он выдаёт ответ:
<F X Y Z> → 100 Y Z
Почему так происходит?
Рассмотрим сначала корректный случай, например, <F Q R S>.
До прогонки:
<F Q R S> → <G Q> R S → 20 R S
В исходной функции F аргумент сопоставится с левой частью второго предложения. 
Вызов заменится на правую часть, где уже будет располагаться вызов <G Q>. Для 
этого вызова применится второе предложение G, поскольку первое будет 
неприменимо. Т.е. сначала второе предложение F, а затем второе предложение G.
После прогонки:
<F Q R S> → 20 R S
После прогонки сразу активируется третье предложение новой функции F, которое 
является комбинацией исходного второго предложения F и исходного второго 
предложения G. Второе предложение новой функции F здесь тоже будет неприменимо, 
поскольку оно является комбинацией исходного второго предложения и первого 
предложения функции G.
Теперь вернёмся к примеру <F X Y Z>. До прогонки активировалось второе 
предложение F, после чего вызов G падал, поскольку ни одно из его предложений 
не применимо. После прогонки становятся неприменимыми второе и третье 
предложения новой функции F (т.к. неприменимы их прообразы в G) и управление 
передаётся на четвёртое предложение.
Т.е. область определения может расширяться на те случаи, где прогоняемый вызов 
должен был падать. (А может и не расширяться, если, к примеру, прогоняемый 
вызов никогда не падает или предложение с этим вызовом самое последнее.)
 
Теперь перехожу к сути письма.
Условимся, что прогонять мы будем только самый первый вызов с пассивным 
аргументом — в этом случае мы избежим нарушения порядка вычислений функций для 
неленивого Рефала. При этом вот ограничении прогонка будет сохранять область 
определения, если дополнить ограниченный Рефал неуспехами.
Определим такой ограниченный Рефал-6 — подмножество Рефала-6, в котором 
предложения могут иметь вид «L-образец, результат». Т.е. в отличие от базисного 
Рефала, в ограниченном Рефале-6 правая часть отделена от левой не равенством, а 
запятой.
Что это даёт. Если в Рефале-6 ни одно из предложений функции оказалось 
неприменимо, то программа аварийно не завершается, вместо этого функция 
вырабатывает особое значение — «неуспех». Если функция возвратила неуспех в 
результатном выражении после знака «=», то программа уже падает с ошибкой 
«неперехваченный неуспех». Если неуспех возник в результатном выражении после 
знака «,», то он может быть перехвачен и обработан программой. Я здесь не буду 
подробно описывать семантику неуспехов, она есть в 
<http://refal.ru/~arklimov/refal6/M3-six.htm>  руководстве к Рефалу-6. Отмечу 
лишь, что в программах ограниченного Рефала-6 управление в случае неуспеха 
будет передаваться на следующее предложение.
Рассмотрим такую программу на ограниченном Рефале-6:
F {
  A e.Z , 1 e.Z;
  s.X e.Z , <G s.X> e.Z;
  t.X e.Z , 100 e.Z;
};

G {
  P , 10;
  Q , 20;
};
Рассмотрим вызов <F X Y Z>. Первое предложение F окажется неприменимо. 
Применится второе предложение. В правой части будет выполняться вызов <G X>. 
Оба предложения функции G окажутся неприменимы — этот вызов вернёт неуспех. 
Поскольку правая часть второго предложения вернула неуспех, управление будет 
передано на третье предложение. Третье предложение успешно вычислится в 100 Y Z.
Для вызова <F> ни одно из предложений F окажется неприменимо, поэтому такой 
вызов выработает неуспех.
Если мы для ограниченного Рефала-6 будем применять прогонку, как она описана у 
Турчина, то область определения меняться не будет. Для этого примера мы получим:
F {
  A e.Z , 1 e.Z;
  P e.Z , 10 e.Z;
  Q e.Z , 20 e.Z;
  t.X e.Z , 100 e.Z;
}
Нетрудно заметить, что область определения не изменилась. Почему? Если до 
прогонки активировалось второе предложение F, но ни один из образцов G не был 
применим, то вырабатывался неуспех и управление передавалось на третье 
предложение старой F. После прогонки оказываются неприменимы оба новых 
предложения и управление естественным путём передаётся на четвёртое предложение 
новой F.
 
Неуспехи были введены в Рефале-4 (Романенко, 1987 ( 
<https://pat.keldysh.ru/~roman/doc/1987-Romanenko--Refal-4_-_rasshirenie_Refala-2.pdf>
 pdf)), правда там они назывались неудачами. Особенностью Рефала-4 было то, что 
он замкнут относительно операции прогонки, при этом допускает образцы общего 
вида (в отличие от L-образцов в ограниченном Рефале). Позже из Рефала-4 
неуспехи перекочевали в Рефал Плюс, а оттуда уже в Рефал-6. Поэтому то, о чём я 
пишу, не новость — это должно было быть очевидно Сергею Романенко, когда он 
проектировал Рефал-4 и Рефал Плюс. Когда-то давно разрабатывался 
суперкомпилятор-оптимизатор для Рефала-6. Если в Рефале-6 на тот момент уже 
были неуспехи, то эта особенность прогонки тоже могла быть известна авторам 
суперкомпилятора.
Так что я не претендую на эпохальное открытие, а делюсь, как мне кажется, 
интересным наблюдением.
 
Суперкомпилятор для ограниченного Рефала будет расширять область определения 
программы по двум причинам. Во-первых, Рефал язык аппликативный, а 
суперкомпилятор реализует ленивую семантику. А вторая причина была подробно 
разобрана в этом письме. И если первая — плата за бо́льшую глубину 
преобразований, то вторую можно преодолеть несколькими способами.
Можно в исходных программах использовать только ортогональные образцы + 
условия-неравенства. Любую программу на ограниченном Рефале (не ограниченном 
Рефале-6) можно преобразовать в эквивалентную с непересекающимися образцами с 
условиями-неравенствами.
Можно к каждой функции неявно добавлять предложение вида
F {
  ...
  e.X = <F$fail e.X>;
}
где F$fail — (примитивная) функция, которая падает на любом аргументе. Такие 
вызовы будут падать в остаточную программу ради сохранения области определения. 
Так сделано в оптимизаторе-прогонщике Рефала-5λ (он не суперкомпилятор, но 
прогонка в нём есть).
И, наконец, можно вместо ограниченного Рефала использовать ограниченный 
Рефал-6. А если определить ограниченный ленивый Рефал-6, то область определения 
программы будет сохраняться всегда.
 
С уважением,
Александр Коновалов
  • Про... Александр Коновалов a . v . konovalov87_AT_mail . ru
    • ... Александр Гусев gusev_aleksandr_AT_mail . ru
      • ... Stelletsky V
        • ... Александр Гусев gusev_aleksandr_AT_mail . ru
    • ... Александр Коновалов a . v . konovalov87_AT_mail . ru
    • ... Александр Коновалов a . v . konovalov87_AT_mail . ru

Ответить