Добрый день!
Александр! Есть 3-й самый простой вариант: добавляете в проблемную функцию еще 
одно (или больше) предложение – вот Вам и обработка неуспеха! Если они что-то 
выводят – можете проанализировать «листинг»…
Я, например, после «сложных» программ, которые могут «свалиться» делаю проверку 
на наличие «дампа» и в этом случае останавливаю выполнение bat-файла, а то и 
шлю себе на почту сообщение об аварии…

 

С уважением,
Василий Стеллецкий
--
Vasily Stelletsky   <mailto:s...@cnshb.ru> mailto:s...@cnshb.ru    
<mailto:sw...@ya.ru> mailto:sw...@ya.ru
 <http://www.cnshb.ru/vniitei/sw/> http://www.cnshb.ru/vniitei/sw/    
<http://sw710.narod.ru> http://sw710.narod.ru

  _____  

From: Александр Гусев gusev_aleksandr_AT_mail.ru [mailto:refal@botik.ru] 
Sent: Wednesday, December 18, 2019 9:44 AM
To: refal@botik.ru
Subject: Re: Прогонка по Турчину и неуспехи

 

Доброго утра!

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

Предлагаю взглянуть на всё с практической точки зрения. Что нужно, чтобы 
обучить с нуля и посадить программировать но Рефале 20-30-100 человек для 
решения практических задач. Проблема обработки неуспехов тут на очень важном 
месте, поскольку самая продвинутая программа может "упасть" из-за оплошности в 
одной вроде бы незначащей функции. И сложность поиска решения мало зависит от 
сложности и важности этой функции.

Напрашиваются два решения: 
1. либо расширять язык обработкой неуспехов, что приведёт, скорее всего, к 
возрастанию сложности понимания языка;
2. либо пытаться решить проблему средствами суперкомпиляции, либо исправляя 
"сомнительные" места, либо на них указывая. Это сильно сложнее, но зато скрыто 
от пользователя - вполне заурядного программиста и ему сильно легче.

Сейчас ни тот ни другой путь ещё не пройден до конца, как я думаю.

Интересно, есть ли ещё варианты?

Да, мне кажется, что базисный Рефал - это минимум, ниже которого опускаться не 
следует. Это сильно сужает гибкость языка и может служить только чисто 
теоретическим целям.

Среда, 18 декабря 2019, 1:16 +03:00 от Александр Коновалов 
a.v.konovalov87_AT_mail.ru <refal@botik.ru>:

Доброй ночи всем!

Пришло в голову одно интересное соображение. Но сначала контекст.

В 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, то область определения 
программы будет сохраняться всегда.

 

С уважением,
Александр Коновалов



С уважением,
Александр Гусев
gusev_aleksa...@mail.ru

  • Про... Александр Коновалов 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

Ответить