Доброе утро, Александр и Василий! Исходное письмо было посвящено не обработке ошибок, а тонкостям семантики прогонки. И именно эту семантику мне хотелось обсудить (надеюсь, мне что-нибудь ответят).
Но тема обработки ошибок в Рефале тоже интересная, и её тоже стоит обсудить. Поэтому я отправляю это письмо с новой темой. По перехвату ошибок у меня тоже есть соображения, но напишу их ближе к вечеру, когда у меня появится время. Также вечером содержательнее отвечу на ваши письма. С уважением, Александр Коновалов From: Stelletsky V swi_AT_cnshb.ru [mailto:refal@botik.ru] Sent: Wednesday, December 18, 2019 10:33 AM To: 'Александр Гусев' <gusev_aleksa...@mail.ru>; refal@botik.ru Subject: RE: Прогонка по Турчину и неуспехи Добрый день! Александр! Есть 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 <mailto: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 <mailto: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 <mailto:gusev_aleksa...@mail.ru>