
- •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.3. Склеивание переменных
Трансформация склеивания переменных есть замена в определении предиката нескольких переменных одной. Трансформация определяет список имен переменных. Переменные из указанного списка переменных заменяются первой переменной. Например, склеивание переменных a, b и c представляет систематическую замену всех вхождений имен b и c на имя a. Необходимо соблюдение условий корректности, гарантирующих эквивалентность определений предиката до и после проведения склеивания. Склеивание переменных является первой трансформацией среди перечисленных в начале разд. 7 четырех видов трансформаций. В результате склеивания уменьшается число переменных, программа становится короче и быстрее. Значительный эффект достигается при склеивании структурных переменных, таких как массивы и списки, поскольку склеивание обычно позволяет избежать копирования структур.
Задача склеивания переменных в данной постановке вряд ли может стать актуальной для оптимизации функциональных программ: в определении функции нет результирующих переменных, и там можно было бы рассматривать лишь склеивание локальных и исходных переменных. Эта задача не возникает для императивных программ, поскольку практически все рассматриваемые здесь склеивания обычно реализуются программистом в императивной программе.
Склеивание переменных рассматривалось ранее в рамках задачи экономии памяти в классических работах А. П. Ершова, С. С. Лаврова, В. В. Мартынюка. Склеивание переменных определялось как выбор переобозначения аргументов и результатов из заданного множества корректных переобозначений, которое позволяет в наибольшей степени уменьшить объем необходимой памяти [2]. В оптимизирующей трансляции императивных программ склеивание переменных обычно реализуется на основе анализа локальных свойств программы [3; 4]. Отметим, что необходимость склеивания переменных обусловлена также тем, что в применяемых методах трансляции конструкций (особенно выражений) происходит включение в программу большого числа дополнительных промежуточных переменных.
В отличие от задачи экономии памяти [2] склеиванию подлежат только те переменные, между которыми имеется информационная связь в определении предиката. Кроме того, склеиваются лишь переменные одного типа, однако допускается различие в параметрах типа. Алгоритм автоматического оптимального склеивания переменных описан в работе [5]. В каждом операторе программы определяются аргументы и результаты оператора. Результат оператора может быть склеен с любым аргументом соответствующего типа при условии, что аргумент не используется далее после завершения исполнения оператора. В работе [5] не рассматриваются сложные формы склеивания переменных структурных типов, когда несколько структурных переменных размещаются внутри некоторой другой переменной. Пример сложной формы склеивания иллюстрируется в разд. 7.6.
7.4. Метод обобщения исходной задачи
Трансформация замены хвостовой рекурсии циклом является весьма эффективной, поскольку устранение рекурсии позволяет провести подстановку определения на место вызова, после чего открывается возможность применения серии других оптимизирующих преобразований. Однако рекурсия в наиболее простом решении задачи обычно не является хвостовой. Существует ряд подходов по приведению рекурсии к хвостовому виду, как правило, автоматическими преобразованиями. Однако автоматическое приведение определения предиката к виду хвостовой рекурсии может быть оправдано лишь в том случае, когда все дальнейшие преобразования совершаются в автоматическом режиме. По нашей точке зрения, неправильно считать приведение к виду хвостовой рекурсии оптимизирующим преобразованием, реализуемым в процессе трансляции. Данное преобразование меняет алгоритм решения задачи, и поэтому должно применяться на стадии выбора алгоритма и программирования.
В качестве адекватной техники программирования предлагается универсальный метод обобщения исходной задачи для получения решения с хвостовой формой рекурсии. Этот метод часто оказывается ключевым для эффективной реализации предикатных программ.
Дадим иллюстрацию метода на примере 5.1 программы умножения через сложение. Как отмечено выше, рекурсия в программе (7.2) не является хвостовой, поскольку после рекурсивного вызова Умн(a – 1, b) в операторе c = b + Умн(a – 1, b) реализуется сложение с b. Чтобы сделать рекурсию хвостовой, необходимо внести сложение с b в рекурсивный вызов. Типичный прием использование дополнительного параметра d в качестве накопителя при суммировании. Рассмотрим обобщенную задачу реализации умножения a * b через сложение с использованием накопителя d. Представим задачу следующим предикатом:
УмнO(nat a, b, d: nat c) pre a ≥ 0 & b ≥ 0 & d ≥ 0 post c = a * b + d;
Тогда Умн(a, b: c) УмнO(a, b, 0: c), и поэтому корректным является следующее определение предиката:
Умн(nat a, b: nat c) (7.3)
pre a ≥ 0 & b ≥ 0
{ УмнO(a, b, 0: c) }
post c = a * b;
Формальное доказательство корректности (при очевидной однозначности и реализуемости) проводится доказательством истинности следующих правил, являющихся упрощенными аналогами правил LS8 и LS9.
Правило LSУ8. PУмн(a, b) ├ true (в качестве предусловия константы 0).
Правило LSУ9. PУмн(a, b) & QУмн(a, b, c)├ PУмнO(a, b, 0) & QУмнO(a, b, 0, c). □
Предикат УмнO имеет следующее определение:
УмнO(nat a, b, d: nat c) (7.4)
pre a ≥ 0 & b ≥ 0 & d ≥ 0
{ if (a = 0) c = d else УмнO(a – 1, b, d + b: c) }
post c = a * b + d;
Доказательство корректности определения проводится так же, как и для определения Умн в примере 5.1 (см. разд. 5.5). □
Проиллюстрируем технику трансформации программы умножения, представленную определениями предикатов Умн и УмнO. На первом этапе применяется трансформация склеивания переменных. Аргумент d склеивается с результатом c, поскольку c информационно связан с d в обеих альтернативах условного оператора. Отметим, что дополнительная переменная, введенная при обобщении задачи, обычно подлежит склеиванию с некоторым результатом. Склеивание d с c будем обозначать c d. В результате склеивания получим следующее определение:
УмнO(nat a, b, c: nat c) { if (a = 0) c = c else УмнO(a – 1, b, с + b: c) } .
Далее применяется трансформация замены хвостовой рекурсии циклом
УмнO(nat a, b, c: nat c) {M: if (a = 0) else |a, b, c| = |a – 1, b, с + b|; goto M } .
После раскрытия группового оператора и оформления цикла получим
УмнO(nat a, b, c: nat c) {for (; a <> 0 ;) {a = a – 1; c = с + b} } . (7.5)
Последней является трансформация подстановки определения (7.5) на место вызова в (7.3). В соответствии с определением подстановки (7.1) получим:
|a, b, c| = | a, b, 0 |; for (; a <> 0 ;) {a = a – 1; c = с + b} .
Раскрывая групповой оператор, получим итоговую программу:
c = 0; for (; a <> 0 ;) {a = a – 1; c = с + b} .
Результатом проведенной серии трансформаций предикатной программы (7.3), (7.4) является представленная императивная программа, по качеству не хуже запрограммированной вручную. Разумеется, данная программа далеко не самая эффективная. В процессорах ЭВМ применяются более изощренные алгоритмы, использующие особенности двоичного представления чисел a и b. В предикатном программировании эффективность конечной программы в значительной степени зависит от выбора эффективного алгоритма, превращаемого в эффективную программу применением последовательности трансформаций.