Трансформация кодирования структурных объектов

Трансформация кодирования структурных объектов реализует представление используемых в программе структурных типов (списков, деревьев и др.) посредством структур более низкого уровня, таких как массивы и указатели. Операции с используемыми в программе структурными типами выражаются в терминах структур низкого уровня. Трансформация реализует систематическую замену вхождений таких операций в программе на соответствующие эквивалентные конструкции, представленные на языке структур низкого уровня. Применение данной трансформации иллюстрируется на следующем примере суммирования списка.

Пример 7.1. Программа суммирования sum(s: c) элементов списка s.

Отметим, что более типичной является программа суммирования элементов массива. Однако массив является объектом логики второго порядка. Список более предпочтителен по сравнению с массивом, поскольку операции со списком выражаются в рамках логики первого порядка.

Спецификацию программы суммирования определим рекурсивным предикатом:

SUM(s, c) @ s = nil ? c = 0 : $ real a. SUM(s.cdr, a) & c = s.car + a . (7.6)

Простейшая программа суммирования тождественна спецификации (7.6).

type seqR = list (real);

sum(seqR s: real c) (7.7)

{ if (s = nil ) c = 0 else c = s.car + sum(s.cdr) }

post SUM(s, c);

Доказательство корректности определения sum реализуется на основе правил LR3 и LR4 по схеме, сформулированной в конце разд. 5.5. Очевидна реализуемость спецификации и однозначность правой части определения. Переменная s является индуктивной. На множестве списков определим отношение порядка: u ⊑ s @ $v. v + u = s. Строгий частичный порядок ⊏ определяется на основе ⊑. Тогда истинно s.cdr ⊏ s. Минимальный элемент nil покрывается условием s = nil в условном операторе. Операции s.car и s.cdr имеют предусловие s ≠ nil. Это предусловие истинно для альтернативы else, и поэтому согласно лемме 5.8 достаточно доказать истинность оператора c = s.car + sum(s.cdr). В соответствии с индуктивным предположением мы можем заменить sum на SUM. □

Рекурсивная программа (7.7) неэффективна, поскольку рекурсия не является хвостовой. Введем накопитель d. Рассмотрим обобщенную задачу суммирования:

sumG(seqR s, real d: real c) post$e. SUM(s, e) & c = e + d;

Ввиду истинности тождества sum(s: c) º sumG(s, 0: c) корректно следующее определение предиката:

sum(seqR s: real c) (7.8)

{ sumG(s, 0: c) }

post$ real e. SUM(s, e) & c = e + d;

Предикат sumG имеет следующее определение:

sumG(seqR s, real d: real c) (7.9)

{ if (s = nil ) c = d else sumG(s.cdr, d + s.car : c) }

post $ real e. SUM(s, e) & c = e + d;

Доказательство корректности определения sumG реализуется аналогично доказательству корректности определения (7.7). Поскольку s.cdr ⊏ s, то вызов sumG в теле (7.9) может быть заменен постусловием sumG. Истинность полученной формулы доказывается применением определения (7.6). □

Хвостовая рекурсия в определении sumG дает возможность провести для программы (7.8), (7.9) следующую серию трансформаций, аналогичную примененной для задачи Умн. Проведем склеивание c ¬ d.

sumG(seqR s, real с: real c) { if (s = nil ) c = c else sumG(s.cdr, c + s.car : c) }

Заменим хвостовую рекурсию циклом. Отметим, что при раскрытии группового оператора необходимо будет поменять порядок присваивания параметрам s и с.

sumG(seqR s, real с: real c) { for (; s <> nil; ) { с = с + s.car; s = s.cdr }

Подставим полученное определение в (7.8).

c = 0; for (; s <> nil; ) { c = с + s.car; s = s.cdr } (7.10)

Программа (7.10) использует три операции со списками. Непосредственная реализация оператора s = s.cdr приводит к копированию большей части списка. Поэтому такая программа неэффективна.

Для программы (7.10) применяется трансформация кодирования списка вырезкой массива. При этом разные значения списка s представляются вырезками одного массива S. Для программы (7.10) необходимо кодировать начальное значение списка s и текущее, причем в начальный момент исполнения программы текущее значение устанавливается равным начальному. Начальное состояние - вырезка S[m..n], текущее - S[j..n]. Массив S и величины j, m, n должны быть определены в программе, причем j - переменная, а m и n могут быть константами.

type SEQR = array (real, M..N);

SEQR S;

int j = m;

Значения границ M и N должны быть достаточными, т. е. M ≤ m, n, j ≤ N. Операции со списком s кодируются следующим образом:

s = nil → j > n

s <> nil → j <= n

s.car → S[j]

s = s.cdr → j = j + 1

Применение трансформации кодирования списка s к программе (7.10) дает итоговую программу.

int j = m; c = 0; for (; j <= n; ) { c = с + S[j]; j = j + 1 }