
lecture7.ppt
- Количество слайдов: 31
31. 10. 12 Лекция 7 Предикатное программирование 7. Технология предикатного программирования Базовые трансформации Подстановка определения на место вызова Замена хвостовой рекурсии циклом Склеивание переменных Метод обобщения исходной задачи Трансформация кодирования структурных объектов Пример. Сортировка простыми вставками
7. Технология предикатного программирования
Предикатная программа транслируется на императивное расширение языка P с применением оптимизирующих трансформаций. Базовыми трансформациями являются: - замена хвостовой рекурсии циклом; - подстановка определения предиката на место его вызова; - склеивание переменных, реализующее замену нескольких переменных одной; - кодирование рекурсивных структур с помощью массивов и указателей. Итоговая программа по эффективности не уступает написанной вручную и, как правило, короче. Технология предикатного программирования позволяет воспроизвести любую реализацию, проводимую в императивном программировании. Для сложных задач предикатная программа на порядок проще ее императивного аналога.
Подстановка определения предиката на место вызова A(x: y) { S } определение предиката на императивном расширении языка P, а A(e: z) вызов предиката в теле предиката B. x, y, z списки переменных, а e список выражений. Подстановка определения предиката на место вызова A(e: z) есть замена вызова композицией: | x | = | e |; { S }; | z | = | y | (7. 1) x и y становятся локальными переменными Необходимо предварительное систематическое переименование переменных. Исполнение (7. 1) эквивалентно исполнению вызова A(e: z). |x| = |e| - групповой оператор присваивания раскрытие группового оператора
| x | = | e |; { S }; | z | = | y | (7. 1) В большинстве случаев | z | = | y | можно устранить, заменив z на y в операторе S x состоит из x 1, x 2, …, xn; а e - e 1, e 2, …, en. Пусть ej - переменная. ej должна быть отлична от xj. Почти всегда замена xj на ej дает эквивалентную программу. Склеивание xj с ej уменьшает число переменных на 1 и позволяет удалить xj = ej в групповом операторе |x| = |e|. Склеивание переменных xj и ej корректно кроме случая, когда xj перевычисляется внутри S, что возможно как результат склеивания xj с другими переменными, а переменная ej используется не только в вызове A(e: z), но и после него.
Замена хвостовой рекурсии циклом Это специальный случаем подстановки определения предиката на место вызова Рекурсивный вызов определяет хвостовую рекурсию, если: • имя вызываемого предиката совпадает с именем определяемого предиката, в теле которого находится вызов; результаты вызова совпадают с результатами – формальными параметрами • вызов является последней исполняемой конструкцией в определении предиката, содержащем вызов. last(S) - множество последних исполняемых конструкций в операторе S. Тогда: last(A; B) = last(B), last(if (C) A else B) = last(A) last(B), last(D(e: z)) = {D(e: z)}, last(A || B) = . Однако если A || B реализуется последовательным исполнением A и B, например, как B; A, то last(A || B) = last(A). Умн(nat a, b: nat c) { if (a = 0) c = 0 else c = b + Умн(a – 1, b) } не хвостовая рекурсия!! D(nat a, b: nat c) { if (a = b) c = a else if (a < b) D(a, b - a: c) else D(a - b, b: c) } это хвостовая рекурсия
Определение предиката A(x: y) { S } Вызов A(e: z) с хвостовой рекурсией внутри S. Тогда этот вызов должен иметь вид A(e: y), т. е. z = y. Подставим определение предиката A на место вызова A(e: y). Получим: |x| = |e|; { S }. Обозначим через S’ оператор, полученный заменой в S подстановкой определения на место вызова A(e: y). Можно заменить в S’ второе вхождение S передачей управления на начало оператора S’. В итоге определение предиката A преобразуется к виду: A(x: y) { M: S’’ }, где S’’ получается из S заменой вызова A(e: y) парой операторов: |x| = |e|; goto M. трансформация замены хвостовой рекурсии циклом.
D(nat a, nat b: nat c) { M: if (a = b) c = a else if (a < b) {|a, b| = |a, b - a|; goto M} else {|a, b| = |a - b, b|; goto M} } Раскроем групповые операторы присваивания, а также заменим фрагмент с операторами перехода на цикл for. Получим: D(nat a, nat b: nat c) { for ( ; ; ) { if (a = b) {c = a; break; } if (a < b) b = b – a else a = a – b } }
Склеивание переменных Задачи экономии памяти в классических работах А. П. Ершова, С. С. Лаврова, В. В. Мартынюка. Трансформация склеивания переменных - замена нескольких переменных одной. a b, c - замена всех вхождений имен b и c на имя a. Склеиваются переменные одного типа, между которыми имеется информационная связь. В каждом операторе программы определяются аргументы и результаты оператора. Условия корректности, гарантирующих эквивалентность определений предиката до и после проведения склеивания: переменная a не является пост-аргументом оператора, в котором проводятся склеивания. Результат оператора может быть склеен с любым аргументом соответствующего типа при условии, что аргумент не используется далее после завершения исполнения оператора.
Метод обобщения исходной задачи Проблема приведения рекурсии к хвостовому виду. Техника автоматических преобразований проблематична. Метод обобщения исходной задачи для получения решения с хвостовой формой рекурсии. Умн(nat a, b: nat c) { if (a = 0) c = 0 else c = b + Умн(a – 1, b) } Прием – использование дополнительного параметра d качестве накопителя. Умн 1(nat a, b, d: nat c) pre a ≥ 0 & b ≥ 0 & d ≥ 0 !!! post c = a b + d; Умн(a, b: c) Умн 1(a, b, 0: c). Тогда: Умн(nat a, b: nat c) { Умн 1(a, b, 0: c) } post c = a b;
Умн 1(nat a, b, d: nat c) { if (a = 0) c = d else Умн 1(a – 1, b, d + b: c) } post c = a b + d; Склеивание c d. Умн 1(nat a, b, c: nat c) { if (a = 0) c = c else Умн 1(a – 1, b, с + b: c) } Замена хвостовой рекурсии циклом: Умн 1(nat a, b, c: nat c) {M: if (a = 0) else {|a, b, c| = |a – 1, b, с + b|; goto M } } Раскрытие группового оператора и оформления цикла: Умн 1(nat a, b, c: nat c) {for (; a != 0; ) {a = a – 1; c = с + b} } Подстановка определения на место вызова |a, b, c| = | a, b, 0 |; for (; a != 0; ) {a = a – 1; c = с + b} Раскрытие группового оператора: c = 0; for (; a != 0; ) {a = a – 1; c = с + b}
Трансформация кодирования рекурсивных структур Пример. Программа суммирования sum(s: c) эл-тов посл-ти s. type seq. R = list (real); formula SUM(seq. R s, real c) = s = nil ? c = 0 : exists real a. SUM(s. cdr, a) & c = s. car + a; sum(seq. R s: real c) { switch (s){ case nil: c = 0 case cons(h, t): c = h + sum(t) } } post SUM(s, c); sum(seq. R s: real c) nil? (s) or cons? (s) = true { if (nil? (s)) c = 0 else {{h = s. car || t = s. cdr}; c = h + sum(t)} } post SUM(s, c); sum(seq. R s: real c) { if (s = nil) c = 0 else c = s. car + sum(s. cdr) } post SUM(s, c);
type seq. R = list (real); formula SUM(seq. R s, real c) = s = nil ? c = 0 : exists real a. SUM(s. cdr, a) & c = s. car + a; sum(seq. R s: real c) { if (s = nil) c = 0 else c = s. car + sum(s. cdr) } post SUM(s, c); Обобщение задачи SUM formula SUMG(seq. R s, real d, c) = e. SUM(s, e) & c = e + d; sum. G(seq. R s, real d: real c) post SUMG(s, d, c); sum(seq. R s: real c) { sum. G(s, 0: c) } post SUM(s, c); sum. G(seq. R s, real d: real c) { if (s = nil ) c = d else sum. G(s. cdr, d + s. car : c) } post SUMG(s, d, c) measure len(s);
sum. G(seq. R s, real d: real c) { if (s = nil ) c = d else sum. G(s. cdr, d + s. car : c) } post SUMG(s, d, c) measure len(s); Склеивание c d. sum. G(seq. R s, real с: c) { if (s = nil ) c = c else sum. G(s. cdr, c + s. car : c) } Замена хвостовой рекурсии циклом. При раскрытии группового оператора меняется порядок присваивания параметрам s и с. sum. G(seq. R s, real с: real c) { for (; s != nil; ) { с = с + s. car; s = s. cdr } Подстановка определения на место вызова real c = 0; for (; s != nil; ) { с = с + s. car; s = s. cdr }
Кодирование списка вырезкой массива Кодирование начального и текущего состояния списка S type SEQR = array (real, M. . N); Начальное S[m. . n], текущее S[j. . n]. SEQR S; int j = m; Значения границ M и N должны быть достаточными, т. е. M ≤ m, n, j ≤ N. Кодирование операций: s = nil → j > n s != nil → j <= n s. car → S[j] s = s. cdr → j = j + 1 Итоговая программа real c = 0; for (int j = m; j <= n; ) { c = с + S[j]; j = j + 1 } real c = 0; for (; s != nil; ) { c = с + s. car; s = s. cdr }
Кодирование списка через указатели type LISTP = struct (real car, LISTP *cdr); LISTP *S; s → S Кодирование операций: s = nil → S = null s != nil → S != null s. car → S->car s = s. cdr → S = S->cdr Итоговая программа real c = 0; for ( ; S != null; ) { c = с + S->car; S = S->cdr } real c = 0; for (; s != nil; ) { c = с + s. car; s = s. cdr }
Пример. Обращение списка a 1 a 2 a 3 an Результат обращения списка: a 1 a 2 a 3 an type T; type LT = list(T); formula reverse(LT s: LT) = (s = nil)? s : reverse(s. cdr) + s. car; reverse. In(LT s: LT s’) pre s nil { reverse. G([s. car], s. cdr: s’) } post s’ = reverse(s); reverse. G(LT s, u: LT s’) post s’ = reverse(s) + u);
reverse. In(LT s: LT s’) pre s nil { reverse. G([s. car], s. cdr: s’) } post s’ = reverse(s); reverse. G(LT s, u: LT s’){ if (u = nil) s’ = s else reverse. G(u. car + s, u. cdr: s’) } post s’ = reverse(s) + u); Склеивание и замена хвостовой рекурсии циклом: reverse. In(LT s: LT s){ reverse. G([s. car], s. cdr: s) }; reverse. G(LT s, u: LT s){ М: if (u = nil) s = s else |s, u| = |u. car + s, u. cdr|; goto M };
Оформление цикла и подстановка на место вызова: reverse. In(LT s: LT s){ LT u; |s, u| = |[s. car], s. cdr|; while (u != nil) { s = u. car + s; u = u. cdr } }; Раскрытие мультиприсваивания: reverse. In(LT s: LT s){ LT u = s. cdr; s = [s. car]; while (u != nil) { s = u. car + s; u = u. cdr } }; type LTP = struct (T car, LTP *cdr); LTP *S; s → S; u → U Кодирование операций: u != nil → U != null s = [s. car] → S->cdr : = null u = s. cdr → U = S->cdr
reverse. In(LT s: LT s){ LT u = s. cdr; s = [s. car]; while (u != nil) { s = u. car + s; u = u. cdr }}; type LTP = struct (T car, LTP *cdr); LTP *S; s → S; u → U Кодирование операций: u != nil → U != null s = [s. car] → S->cdr : = null; u = s. cdr → U = S->cdr u = u. cdr → U = U->cdr; s = u. car + s → U. cdr = s; S = U // портится U s = u. car + s; u = u. cdr → LTP* a = U; U = U->cdr; a->cdr = S; S = a reverse. In(LTP* S) { LTP* U = S->cdr; S->cdr = null; while (U != null) {LTP* a = U; U = U->cdr; a->cdr = S; S = a } };
method Reverse. In. Place ( ) returns ( reverse : Node) requires Val id ( ) ; modifies f o o t p r i n t ; ensures reverse 6= nul l ^ reverse. Val id ( ) ; ensures fresh ( reverse. f o o t p r i n t. . old ( f o o t p r i n t ) ) ; ensures | reverse. l i s t | = | old ( l i s t ) | ; ensures (8 i : int ¤ 0 i ^ i < | old ( l i s t ) | =) old ( l i s t ) [ i ] = reverse. l i s t [ | old ( l i s t )|. . 1. . i ] ) ; { var current : Node ; current : = next ; reverse : = this ; reverse. next : = null ; reverse. f o o t p r i n t : = { reverse } ; reverse. l i s t : = [ data ] ; while ( cur rent 6= nul l ) invar iant reverse 6= nul l ^ reverse. Val id ( ) ; invar iant reverse. f o o t p r i n t old ( f o o t p invar iant cur rent = nul l =) | old ( l i s t ) | = | reverse. l i s t | ; invar iant cur rent 6= nul l =) cur rent. Val id ( ) ^ cur rent 2 old ( f o o t p r i n t ) ^ cur rent. f o o t p r i n t old ( f cur rent. f o o t p r i n t , reverse. f o o t p r i n t ^ | old ( l i s t ) | = | reverse. l i s t | + | cur rent. l i s t | ^ (8 i : int ¤ 0 i ^ i < | cur rent. l i s t | =) cur rent. l i s t [ i ] = old ( l i s t ) [ | reverse. l i s t |+ i ] ) ; invar iant (8 i : int ¤ 0 i ^ i < | reverse. l i s t | =) old ( l i s t ) [ i ] = reverse. l i s t [ | reverse. l i s t |. . 1. . i ] ) ; { var nx : Node ; nx : = current. next ; assert nx 6= nul l =) (8 i : int ¤ 0 i ^ i < | nx. l i s t | =) cur rent. l i s t [1+ i ] = nx. l i s t [ i ] ) ; / / The s ta t e looks l i k e : . . . , reverse , cur rent , nx , . . . assert cur rent. data = cur rent. l i s t [ 0 ] ; current. next : = reverse ; cur rent. f o o t p r i n t : = { cur rent } [ reverse. f o o t p r i n t ; cur rent. l i s t : = [ cur rent. data ] + reverse. l i s t ; reverse : = current ; current : = nx ;
Пример. Сортировка простыми вставками type T; // произвольный тип с линейным порядком “ ” nat n; // n + 1 – число элементов сортируемого массива type natn = 0. . n; type Arn = array (natn, T); Спецификация задачи сортировки: sort(Arn a: a’) post perm(a, a’) & sorted(a’) formula sorted(Arn a) = natn i, j. i < j a[i] a[j]; type F = subtype (predicate (natn: natn) f: bijective(f)); formula perm(Arn a, b) = F f. natn j. a[j] = b[f(j)]; Сведение к более общей задаче: sort(Arn a: Arn a’) { sort 1(a, 0: a') } post perm(a, a’) & sorted(a’); sort 1(Arn a, natn m: Arn a’) pre sorted(a, m) post perm(a, a’) & sorted(a’); formula sorted(Arn a, natn m) = i, j = 0. . m. i < j a[i] a[j];
sort 1(Arn a, natn m: Arn a’) pre sorted(a, m) { if (m = n) a’ = a else pop_into(a, m, a[m+1]: Arn c); sort 1(c, m+1: a’) } post perm(a, a’) & sorted(a’); pop_into(Arn a, natn m, T e: Arn a’) pre m < n & sorted(a, m) & e = a[m+1] post perm(a with [m+1: e], a’) & sorted(a’, m+1); Необходимо обобщение pop_into s sort(s) u b v sort 1(u, b, v) u pop_into(u, b) y pop_into(y, z, b) b z b
sort 1(Arn a, natn m: Arn a’) pre sorted(a, m) { if (m = n) a’ = a else pop_into(a, m+1, m, a[m+1]: Arn c); sort 1(c, m+1: a’) } post perm(a, a’) & sorted(a’); formula sorted(Arn a, natn k, m) = i, j = k. . m. i < j a[i] a[j]; pop_into(Arn a, natn k, m, T e: Arn a’) // k – позиция дырки pre m < n & k > 0 & sorted(a, k-1) & sorted(a, k+1, m+1) & (k > m or e < a[k+1] & a[k-1] <= a[k+1]) post perm(a with [k: e], a’) & sorted(a’, m+1); pop_into(Arn a, natn k, m, T e: Arn a’) // k – позиция дырки { if (a[k-1] <= e) a’ = a with [k: e] else { Arn b = a with [k: a[k-1]]; if (k = 1) a’ = b with [0: e] else pop_into(b, k-1, m, e: a’) } }
Недостаток алгоритма: если a[m] <= a[m+1] при вызове pop_into, то элемент a[m+1] сначала считывается в переменную e, а затем записывается назад в массив a sort 1(Arn a, natn m: Arn a’) pre sorted(a, m) { if (m = n) a’ = a else { T e = a[m+1]; if (a[m] <= e) sort 1(a, m+1: a’) else { pop_into(a, m+1, m, e: Arn c); sort 1(c, m+1: a’) } } } post perm(a, a’) & sorted(a’); Изменился интерфейс pop_into
Улучшенная версия pop_into(Arn a, natn k, m, T e: Arn a’) // k – позиция дырки pre m < n & k > 0 & sorted(a, k-1) & sorted(a, k+1, m+1) & (k > m or e < a[k+1] & a[k-1] <= a[k+1]) & a[k-1] > e { Arn b = a with [k: a[k-1]]; if (k = 1) a’ = b with [0: e] else if (b[k-2] <= e) a’ = b with [k-1: e] else pop_into(b, k-1, m, e: a’) } post perm(a with [k: e], a’) & sorted(a’, m+1); Склеивания a a’ sort(Arn a: a) { sort 1(a, 0: a) };
a a’, c sort 1(Arn a, natn m: a) pre sorted(a, m) { if (m = n) a = a else { T e = a[m+1]; if (a[m] <= e) sort 1(a, m+1: a) else { pop_into(a, m+1, m, e: Arn a); sort 1(a, m+1: a) } } } a a’, b pop_into(Arn a, natn k, m, T e: a) // k – позиция дырки { a = a with [k: a[k-1]]; if (k = 1) a = a with [0: e] else if (a[k-2] <= e) a = a with [k-1: e] else pop_into(a, k-1, m, e: a) };
Замена хвостовой рекурсии циклом. Раскрытие групповых операторов и модификаторов. sort 1(Arn a, natn m: a) pre sorted(a, m) { M: if (m = n) a = a else { T e = a[m+1]; if (a[m] <= e) { m = m+1; goto M; } else { pop_into(a, m+1, m, e: Arn a); m = m+1; goto M; } } } pop_into(Arn a, natn k, m, T e: a) // k – позиция дырки { M: a[k] = a[k-1]; if (k = 1) a[0] = e else if (a[k-2] <= e) a[k-1] = e else { k = k - 1; goto M; } };
Оформление циклов. Упрощения. sort 1(Arn a, natn m: a) { for( ; m = n; m = m+1) { T e = a[m+1]; if (a[m] > e) pop_into(a, m+1, m, e: Arn a); } } pop_into(Arn a, natn k, m, T e: a) // k – позиция дырки { for( ; ; k = k - 1) { a[k] = a[k-1]; if (k = 1) { a[0] = e; break; } else if (a[k-2] <= e) { a[k-1] = e; break; } } };
Подстановка pop_into на место вызова в sort 1 и sort 1 в sort. Упрощения. sort 1(Arn a, natn m: a) { for( ; m = n; m = m+1) { T e = a[m+1]; if (a[m] > e) //pop_into(a, m+1, m, e: Arn a); |a, k, m, e| = |a, m+1, m, e|; for( ; ; k = k - 1) { a[k] = a[k-1]; if (k = 1) { a[0] = e; break; } else if (a[k-2] <= e) { a[k-1] = e; break; } } } }
Подстановка sort 1 на место вызова в sort. Упрощения. sort(Arn a: a) { // sort 1(a, 0: a) // |a, m| = |a, 0|; for( m = 0 ; m = n; m = m+1) { T e = a[m+1]; if (a[m] > e) //pop_into(a, m+1, m, e: Arn a); for( k = m+1; ; k = k - 1) { a[k] = a[k-1]; if (k = 1) { a[0] = e; break; } else if (a[k-2] <= e) { a[k-1] = e; break; } } } }