
- •2.6. Заключение .............................................................................................................. 22
- •4. Язык исчисления вычислимых предикатов,
- •4.12. Однозначность предикатов .......................................................................................... 45
- •5. Семантика языка предикатного программирования.
- •6.9. Формулы .......................................................................................................................... 78
- •6.9. Процессы ......................................................................................................................... 79
- •6.11. Императивное расширение .......................................................................................... 82
- •7.8. Заключение ................................................................................................................... 108
- •Введение в курс предикатного программирования
- •1. Общее понятие программы
- •1.1. Автоматическая вычислимость
- •1.2. Спецификация программы
- •1.3. Формы спецификации программы
- •Список литературы
- •2. Корректность программ с предикатной спецификацией
- •2.1. Предикатная спецификация программы
- •2.2. Логическая семантика языка программирования
- •2.3. Модель корректности программы
- •2.4. Система правил доказательства корректности операторов
- •2.4.1. Правила для корректного оператора
- •2.4.2. Правила корректности для параллельного оператора
- •2.4.3. Правила корректности для оператора суперпозиции
- •2.4.4. Правила корректности для условного оператора
- •2.5. Система правил вывода программы из спецификации
- •2.5.1. Однозначность предикатов
- •2.5.2. Теорема тождества спецификации и программы
- •2.5.3. Правила корректности для параллельного оператора
- •2.5.4. Правила корректности для оператора суперпозиции
- •2.5.5. Правила корректности для условного оператора
- •2.6. Заключение
- •Список литературы
- •3. Математические основы
- •3.1. Отношения порядка
- •3.2. Наименьшая неподвижная точка
- •3.3. Математическая индукция
- •Список литературы
- •4. Язык исчисления вычислимых предикатов, его логическая и операционная семантика
- •4.1. Структура программы на языке ccp
- •4.2. Система типов данных
- •4.3. Логическая и операционная семантика языка ccp
- •4.4. Семантика вызова предиката
- •4.5. Оператор суперпозиции
- •4.6. Параллельный оператор
- •4.7. Условный оператор
- •4.8. Конструктор предиката
- •4.9. Конструктор массива
- •4.10. Программа
- •4.11. Рекурсивные определения предикатов
- •4.12. Однозначность предикатов
- •Список литературы
- •5. Семантика языка предикатного программирования. Методы доказательства корректности предикатных программ
- •5.1. Язык p1: подстановка определения предиката на место вызова
- •5.2. Язык p2: оператор суперпозиции и параллельный оператор общего вида
- •5.3. Язык p2: другое обобщение оператора суперпозиции
- •5.4. Язык p3: выражения
- •5.5. Методы доказательства корректности рекурсивных программ
- •6. Язык предикатного программирования
- •6.1. Введение
- •6.2. Лексемы
- •6.3. Предикаты
- •6.3.1. Определение предиката
- •6.3.2. Спецификация предиката
- •6.3.3. Вызов предиката
- •6.4. Программа
- •6.5. Операторы
- •6.6. Выражения
- •6.7. Типы
- •6.8. Массивы
- •6.8.1. Описание типа массива
- •6.8.2. Вырезка массива
- •6.8.3. Определение массива
- •6.8.4. Объединение массивов
- •6.9. Формулы
- •6.10. Процессы
- •6.11. Императивное расширение
- •Список литературы
- •7. Технология предикатного программирования
- •7.1. Подстановка определения предиката на место вызова
- •7.2. Замена хвостовой рекурсии циклом
- •7.3. Склеивание переменных
- •7.4. Метод обобщения исходной задачи
- •7.5. Трансформация кодирования структурных объектов
- •7.6. Пример. Сортировка простыми вставками
- •7.7. Гиперфункции
- •7.8. Заключение
- •Список литература
7.5. Трансформация кодирования структурных объектов
Трансформация кодирования структурных объектов реализует представление используемых в программе структурных типов (списков, деревьев и др.) посредством структур более низкого уровня, таких как массивы и указатели. Операции с используемыми в программе структурными типами выражаются в терминах структур низкого уровня. Трансформация реализует систематическую замену вхождений таких операций в программе на соответствующие эквивалентные конструкции, представленные на языке структур низкого уровня. Применение данной трансформации иллюстрируется на следующем примере суммирования списка.
Пример 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 }