
- •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.2. Замена хвостовой рекурсии циклом
Подстановка определения нерекурсивного предиката на место вызова является эффективной при наличии в программе одного вызова. Подстановка определения рекурсивного предиката усложняет программу и поэтому сомнительна, хотя иногда может принести пользу; в любом случае такое преобразование считается экзотическим. Для рекурсивно определяемого предиката эффективным является другое преобразование замена хвостовой рекурсии циклом. Тем не менее, оказывается, это преобразование является специальным случаем подстановки определения предиката на место вызова.
Существует специальный случай рекурсии, называемый хвостовой рекурсией (tail-рекурсией в языке Лисп [1]), когда можно заменить рекурсию циклом. Рекурсивный вызов предиката определяет хвостовую рекурсию, если:
имя вызываемого предиката совпадает с именем определяемого предиката, в теле которого находится вызов;
вызов является последней исполняемой конструкцией в определении предиката, содержащем вызов.
Через 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).
Понятие хвостовой рекурсии проиллюстрируем на примерах 5.1 и 5.2 (см. разд. 5.5). В программе умножения через сложение (см. пример 5.1)
Умн(nat a, b: nat c) { if (a = 0) c = 0 else c = b + Умн(a – 1, b) } (7.2)
рекурсивный вызов Умн(a – 1, b) не является хвостовым, поскольку после завершения исполнения вызова исполняется операция “+”. Хвостовой является рекурсия для каждого из двух рекурсивных вызовов в программе вычисления наибольшего общего делителя (см. пример 5.2):
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, поскольку хвостовой вызов с другим набором результатов, кроме y, смысла не имеет. Подставим определение предиката A на место вызова A(e: y). В соответствии с изложенным в конце разд. 7.1 результатом подстановки определения предиката A является | 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. Данное преобразование есть трансформация замены хвостовой рекурсии циклом.
Если в рекурсивном определении предиката A все рекурсивные вызовы имеют вид хвостовой рекурсии, то применение трансформации ко всем этим вызовам преобразует тело предиката в цикл, а определении предиката A в нерекурсивное определение. После этой трансформации становится эффективной постановка определения предиката A на место вызова A в теле другого предиката. А после проведения подстановки вызова A становится возможной серия других преобразований.
Применение трансформации замены хвостовой рекурсии циклом покажем для программы вычисления наибольшего общего делителя. Трансформация двух рекурсивных вызовов предиката D дает следующую программу:
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
}
}
Замена фрагмента с операторами перехода на цикл for не меняет процесса исполнения. Преобразование такого рода, улучшающее структуру программы, будем называть оформлением.