Open Mazdaywik opened 5 years ago
Задача подходит для курсовой работы по компиляторам. Какие пункты выносятся в учебную работу и в каком объёме — обсудим позже.
Рассмотрим уравнение
P : L
Где P
и L
могут быть любыми образцовыми выражениями. Есть три граничных случая, когда решение уравнения можно записать в виде набора наборов сужений и присваиваний.
L
— L-выражение. Решается алгоритмом обобщённого сопоставления Турчина.P ≡ E
— объектное выражение (не содержит переменных). Решение уравнения E : L
есть обычное сопоставление с образцом, происходящее во время выполнения программы.P ≡ e.X
— e-переменная. Решение уравнения есть сужение e.X → L
(с точностью до имён переменных)Нужно разработать алгоритм обобщённого сопоставления, который работал бы в этих трёх граничных случаях, а также где-то рядом с ними. Например, случай 2 можно расширить, разрешив в P s-переменные.
Вообще, если наоборот P
— L-выражение, а L
— произвольное выражение, то можно поменять местами части уравнения и решить обычным алгоритмом обобщённого сопоставления. Однако, надо иметь ввиду порядок удлинения открытых e-переменных и соответствующим образом упорядочить решения. Случай, когда P
— L-выражение, покрывает граничные случаи 2 и 3.
Но не нужно делать проверку, что если L
— не L-выражение, а P
— L-выражение, то нужно поменять их местами и решать. Нужно разработать алгоритм обобщённого сопоставления, который в этой ситуации не будет выдавать Undefined
, а будет выдавать правильный ответ.
UPD: вынесено в отдельную задачу: https://github.com/bmstu-iu9/refal-5-lambda/issues/322.
Задача не была выбрана в качестве курсовой.
Задача не была выбрана в качестве ВКР.
Можно разрешить сопоставления с повторными te-переменными в сужениях, если они потом подставляются в L-образец. Проблем быть не должно.
Система присваиваний v.L1 ← v.R
⋀ v.L2 ← v.R
означает, что переменные левой части v.L1
и v.L2
равны. Предлагается следующий алгоритм для этого частного случая:
v.L1
, v.L2
,v.New
,v.L1 → v.New
, v.L2 → v.New
, v.New ← v.R
.Зачем нужна новая переменная и почему нельзя v.L1 → v.L2
или v.L2 → v.L1
в зависимости от того, какой тип меньше? Чтобы проще было проверять на неразменные переменные (#231). Неразменная переменная не может быть повторной. Если у нас есть список сужений v.L1 → v.New
, v.L2 → v.New
и список неразменных переменных, то достаточно проверить только левые части сужений. Для случаев вида v.L1 → v.L2
нужно (а) искать сужения с повторными переменными, (б) в них проверять обе стороны. Первый вариант проще.
Таким же образом нужно будет переделать и сопоставление с повторными s-переменными, т.к. и они тоже могут быть неразменными. Например, если s-переменная появилась в образце условия:
F {
s.X e.Y, <G e.Y> : s.Z = <Eq s.X s.Z>;
}
$DRIVE Eq {
s.1 s.1 = True;
s.1 s.2 = False;
}
Если разрешать сужения s.X → s.Z
или s.Z → s.X
или s.X → s.New
⋀ s.Z → s.New
, то получится вот что:
F {
s.New e.Y, <G e.Y> : s.New = True;
s.X e.Y, <G e.Y> : s.Z = False;
}
Вызов <G e.Y>
в условии будет вычисляться дважды.
Допустим, у нас есть уравнение вида e.X : e.B P e.E
в системе уравнений. Мы делаем шаг решения вида e.X → e.1 e.2
, e.1 ← e.B
, e.2 : P e.E
и продолжаем решать систему.
Ограничение: в продолжении решения этой системы должно быть не более одного решения.
Проблема
Турчин когда-то описал прогонку функций (Турчин, 1972, Турчин, 1974), однако, описанный алгоритм прогонки работает только для ограниченного Рефала — базисного Рефала, в образцах которого запрещены t-переменные и открытые и повторные e-переменные (т.н. L-выражения).
Алгоритм естественно расширяется на неповторные t-переменные, поэтому далее мы будем под L-выражениями подразумевать образцы, в которых запрещены открытые e-переменные и повторные t- и e-переменные.
Прогонка для функций Рефала-5 общего вида в общем случае не разрешима. Нужен переход как минимум в формализм Рефала-4 или Рефала Плюс (последний основан на Рефале-4 и включает возможности Рефала-5 как синтаксический сахар над Рефалом-4), но и там всё не так просто. Формально в Рефале Плюс (и в Рефале-6) можно встроить тело функции на место её вызова, но теории достижения содержательной цели прогонки не сформулировано (Романенко, 1987а, Романенко, 1987б).
Напомним, что есть содержательная цель прогонки. Выполнение программы на Рефале в общем случае есть цепь операций синтеза (построения результатных выражений) и последующего анализа (сопоставления с образцом). В случае базисного Рефала результатные выражения строятся только для аргументов функции, для Рефала-5 операции синтеза также включают и левые части условий (см. также #248). В общем, имеем цепочку:
Содержательная цель прогонки — заменить участок
A′ S′ A″ S″
участкомA* S*
, гдеA*
включает в себя в некотором смысле композицию анализаA′
иA″
, аS*
— композициюS′
иS″
. Т.е. устранить необходимость в построении выраженияS′
, которое затем снова начнёт разбираться.Так вот. Теории, позволяющей в рамках Рефала-4/Рефала Плюс полностью прогнать функцию пока не разработано. Исследования Сергея Романенко прервались на препринте Романенко, 1987б, где в конце так и написано:
Так что для полноценной прогонки нужно (а) переходить в более широкий формализм, (б) для нового формализма разрабатывать алгоритм на основе имеющегося исчисления.
Решение
Но если теория говорит нам, что решения в общем виде не существует (либо оно не известно), то всегда можно поискать ограниченные, но часто появляющиеся на практике частные случаи и решать задачу уже для них.
Переходить в формализм Рефала-4 на уровне промежуточного представления пока не предлагается, предлагается так и оставаться в рамках базисного Рефала с условиями, которое сейчас используется между рассахаривателем и генератором RASL’а.
На самом деле уже заложены возможности для обработки частных случаев. Так например, алгоритм обобщённого сопоставления не требует L-выражения справа, вместо этого пытается решить уравнение до тех пор, пока методы решения с L-выражениями работают. Если эти методы становятся неприменимы, возвращается признак
Undefined
, говорящий о том, что решения нет (см. #156).Кроме того, алгоритм уже имеет некоторые простые расширения, выводящие его за рамки L-выражений. Например, обнаруживается, что уравнение
A e.X : B e.1 X e.2
решений не имеет. Если повторная переменная в правой части получила два s-значения (символы или s-переменные), то разрешается параSVAL′ : SVAL″
(а она всегда разрешается) независимо от типа повторной переменной. Исходный алгоритм Турчина разрешает быть повторными только s-переменным.Турчинский алгоритм прогоняет все предложения исходной функции за раз. Реализованный алгоритм прогоняет за раз по одному предложению. Если какое-то предложение прогнать нельзя, строится вызов «остаточной» функции
Func*N
, полученной из исходной вычёркиванием первых N предложений. Это тоже ad hoc расширение, позволяющее оптимизировать функции, у которых только часть предложений относятся к ограниченному Рефалу.Предлагаемые расширения
Ревизия алгоритма обобщённого сопоставления
В первую очередь нужно провести ревизию алгоритма обобщённого сопоставления. На данный момент он может вернуть
Undefined
там, где на самом деле можно доказать отсутствие решения. Примеры мне сейчас искать лень, позже добавлю в комментариях.Открытые переменные в правой части уравнения
Пусть у нас есть такие две функции:
Тогда, если
Pf
является L-выражением, аPg
содержит ровно одну открытую переменную, то кPf
можно применить подстановкуe.X → Pg
и таким образом прогнатьG
на одно предложение (получив вызовG*1
).В примере аргументом функции
G
является переменнаяe.X
, но случай можно обобщить и для более сложных аргументов. В общем случае, если в наборе сужений для уравненияarg : Pg
есть только одна открытая переменная, то подстановку набора сужений в L-выражение применять безопасно.Если в решении открытых переменных несколько, то подстановка сужений безопасна только тогда, когда их порядок не меняется. Например, здесь она безопасна:
Но если в вызове заменить
e.X
наe.Z
, семантика нарушится.Кроме того, безопасно решать уравнение вида
T1 … Tn : e.1 P e.2
, гдеT1
…Tn
— термы (символы, s-, t-переменные, скобочные термы). Можно статически перебрать все возможные удлинения переменнойe.1
и получить упорядоченный набор решений. Случай на практике распространён, особенно в виде условийгде переменная
s.X
входит в образец. На прогонку условий есть отдельная задача #248.Не-L-образцы в вызывающей функции
Тема затронута в #231, там же есть примеры.
Если мы имеем не-L-образец в вызывающей функции, то можем применять сужения только к t- и e-переменным, которые (а) входят в образец один раз и (б) означиваются до цикла удлинения открытой переменной. Реализация этого расширения требует понятия неразменных переменных, предложенных в #231.
Повторные t- или e-переменные в правой части уравнения
Будем говорить, что выражение L-подобно, если стирание имён переменных обращает его в жёсткое выражение. Повторные переменные следует по-разному рассматривать в L-подобных образцах и не-L-подобных.
Текущая реализация алгоритма сначала рассматривает правую часть как жёсткое выражение, т.е. имена переменных в правой части не учитывает. Если правая часть оказалась не-L-подобной, то возвращается
Undefined
(либо «решений нет», если это удалось выявить). В противном случае в построенном наборе присваиваний находятся присваивания кратным переменным видаE1 ← var
иE2 ← var
, одно из присваиваний сохраняется в результате, второе стирается и решается уравнение видаE1 : E2
.Это уравнение, в отличие от исходного, симметрично и потенциально содержит слева и справа переменные одного сорта. В текущей реализации оно решается либо для случая, когда и
E1
, иE2
являются s-значениями (s-переменные или символы), или дословно совпадают. Способ решения уравненияS1 : S2
описан ещё Турчиным, он приводит либо к успеху, либо к сужениюs.X → S2
, либо к неуспеху. Второй случай, очевидно, тождество.Поэтому первое направление ad hoc-расширений — это развитие методов решения уравнений вида
E1 : E2
.Второе направление расширений — решение уравнений с не-L-подобной правой частью. Например, уравнений вида
E : e.X e.Y (e.Y)
. Здесь нет открытых переменных, т.к. второе вхождение закрытое, а первое — повторное. Можно ли объединить методы решения уравнений с открытыми переменными с расширенными методами решения уравненийE1 : E2
предыдущего абзаца, чтобы решать уравнения этого вида — тут нужно разбираться.Прогонка функций в предложениях с условиями
Текущая версия эти предложения просто игнорирует. (Это неправильно, поскольку должна на самом деле остужать функции drive и inline с холодными аргументами, поскольку они могут блокировать другие функции, которые можно специализировать.)
Более продвинутое решение — нужно прогонять вызовы в первом результатном выражении (в первом условии или правой части без условий), а в остальных — только встраивать.
Но наиболее общее решение — учитывать разновидности переменных. Переменные из первого образца можно сужать, последующие — неразменные. Подробности в уже упомянутой задаче #231.
Прогонка функций с условиями
Если предложение прогоняемой функции имеет вид
Pat = Res
, то всё понятно. Сопоставляем аргумент сPat
, получаем набор присваиваний и сужений. Присваивания применяем кRes
, сужения к вызывающей функции, вызов заменяем на модифицированныйRes
. Понятно, что тут тоже вагон нюансов, которые рассматрваются, например, здесь или в #230 и в #231.Но вот если прогоняемое предложение имеет вид
Pat, Res1 : Pat1 = Res;
, становится уже сложнее. Допустим, мы решили уравнениеarg : Pat
. Что дальше? ПодставитьRes1 : Pat1 = Res
на место вызова нельзя. Добавлять условие к предложению вызывающей функции? Наверное, можно в каких-то случаях, но слишком не очевидно влияние на семантику. Можно, наверное, подставить условие, если оно пассивно, но тоже надо думать.Поэтому возможное решение — если прогоняемая функция содержит условия, то её можно при помощи
Desugaring-UnCondition
привести к набору функций без условий, каждую из которых рассматривать как прогоняемую (или встраиваемую в зависимости от типа исходной функции). Но тоже нужно проанализировать это решение на предмет деградации производительности.Возврат условий из функции обобщённого сопоставления
Функция обобщённого сопоставления в норме или возвращает набор пар наборов сужений и присваиваний (в том числе пустой, если решений нет), или признак невозможности сопоставления
Undefined
.Undefined
возвращается в случае, когда при решении системы получились уравнения, которые невозможно разрешить статически (в смысле, алгоритм не знает как). В этом случае предлагается отказаться от прогонки данного вызова функции (либо в дальнейшем от прогонки условия, #248). Т.е. частично проведённый анализ во время компиляции отбрасывается и переносится на стадию выполнения.Можно в случае неопределённости возвращать сужения, присваивания и неразрешённые уравнения. Вызывающий код будет применять присваивания к правой части прогоняемой функции, сужения к предложению вызывающей функции, а неразрешённые уравнения добавлять как условия к анализируемому предложению.
Но тут нужно понять, не будет ли оверхед на условия выше, чем отказ от подобного фокуса. Возможно, понять экспериментально.
Неразрешённые уравнения могут быть двух видов: вида
E : L
, гдеE
— фрагмент аргумента, аL
— фрагмент образца прогоняемой функции, иE1 : E2
, где обе части — фрагменты аргумента, присвоенные повторной переменной. Для этих двух видов уравнений, очевидно, потребуются разные способы построить условие.Замечу, что используется термин неразрешённые уравнения, а не неразрешимые. Некоторые из уравнений могут быть разрешимы методами теории уравнений в словах, но они сложны и поэтому не будут реализовываться в компиляторе. Поэтому такая терминология.