Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
учебное_пособие_флп.doc
Скачиваний:
1
Добавлен:
01.05.2025
Размер:
675.84 Кб
Скачать

2.4.Использование рекурсии

Рекурсия – способ описания функций или процессов через самих себя. Рекурсия используется как основа для записи рекурсивных конструкций.

Рекурсивность – это свойство языка программирования, состоящее в том, что конструкции языка допускают включение в себя в явном виде конструкции того же типа.

Именно это обстоятельство позволяет записывать любую программу с использованием небольшого количества типов логических структур. В логическом программировании такое поведение обычно достигается за счет использования рекурсивных процедур, то есть процедур, имя которых встречается по крайней мере в одном вызове из ее тела. Итерационные процедуры, таким образом, являются частным случаем рекурсивных процедур.

Введем в рассмотрение еще одно отношение –ancestor (предок). Определим его в терминах уже известного отношения parent. Отношение ancestor может быть представлено с помощью двух правил:

      • Правило для определения прямых предков (1);

      • Правило для определения отдаленных предков, то есть таких, между которыми существует цепочка родительских связей типа parent. (2)

Рис.2.4.Отношение ancestor для прямого(правило 1) и отдаленного предков(правило2)

Для всех X и Z,

1) X - предок Z, если

X – родитель Z.

Второе правило (б) формулируется так:

Для всех X и Z,

2) если имеется такой Y, что

а) X -родитель Y(прямые предки) и

б) Y –предок Z. (отдаленные предки)

Эти утверждения можно перевести на Пролог таким образом:

1) ancestor(X,Z):-

parent(X,Z).

2) ancestor(X,Z):-

parent(X,Y),

ancestor(Y,Z).

Отсюда видно (2), что было использовано утверждение ancestor, которое само еще не было полностью определено. Такие определения называются рекурсивными. Отношение, представленное в заголовке правила (2) зависит от более простой версии самого себя- от подцели ancestor(Y,Z).

Предложенная задача содержит в себе подзадачу такого же типа. При работе с такими задачами должно существовать обязательное условие выхода из рекурсии, иначе рекурсия может оказаться бесконечной, а правила бесполезными. Окончательное решение задачи откладывается до тех пор, пока не сработает утверждение, которое и определяет условие выхода из рекурсии. Ответственность за выход из рекурсии лежит на программисте.

Состав рекурсивной процедуры :

      • Нерекурсивное предложение (1), определяющее вид процедуры в момент ее прекращения (терминальный случай –базис рекурсии).

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

2.5.Декларативная и процедурная трактовка программы

Декларативное значение определяет то, каким должен быть результат работы программы. Процедурное значение определяет способ получения этого результата, то есть показывает, как на самом деле Пролог-система проводит обработку представленных в программе отношений. Процедура выводит сведения об истинности или ложности целей и в случае истинности целей показывает при каких значениях переменных это достигается.

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

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

Прогресс в технологии программирования достигается за счет все более глубокого ухода к декларативным аспектам (они легче понимаются и легче поддаются формальному описанию). Желательно все процедурные детали переложить на саму систему.

Но система Пролог не всегда, к сожалению, способна проработать все процедурные детали. Часто это бывает связано с тем, что стратегия решения задачи в компиляторе – другая и не совпадает с порядком написания предложений в программе (например, рекурсивный вызов стоит раньше других подцелей предложения, из-за этого не происходит надежной обработки). В этой ситуации необходимо сначала сосредоточиться на декларативных аспектах программы, затем оттестировать программу, и в случае ее отказа, попытаться переупорядочить предложения и цели.

Пример. Вернемся к задаче о родственных отношениях. Предположим, что мы определили отношение – предок так (см рис.2.5. 2)):

Рис.2.5. Влияние переупорядочения предложений в программе с использованием рекурсии

На рис 2) с декларативной точки зрения все в порядке.

Но при процедурной трактовке смысл становится совершенно другим. Переменная Z здесь не конкретизируется в момент обработки рекурсивной подцели ancestor(X,Y). У нас первым стояло предложение parent(Y,Z). Оно вырабатывало новое значение переменной Y, а уже потом использовало это новое значение в рекурсивной подцели ancestor(X,Y). Теперь этого нет. К чему это приведет? Это приведет к тому, что когда компилятор будет пытаться выполнить запрос к процедуре ancestor, то вначале он отыщет правильные ответы, а затем будет выполнять рекурсивные действия вплоть до исчерпания доступного объема памяти. Это происходит потому, что рекурсивный вызов не является последним в предложении, а стоит раньше других подцелей. Cтратегия же решения задачи в компиляторе – другая, поэтому не будет надежной обработки.

Попробуйте самостоятельно сформулировать правило, отображенное на рис 1). А теперь попытайтесь объяснить, что произойдет с программой, в которой процедура ancestor представлена так как на рис 3).

Когда мы пишем программу, то используем процедурное понимание. Когда мы определяем значение программы, рассматривая это значение с точки зрения истинности, то применяем декларативное понимание.

В “чистом прологе” декларативное значение программ не зависит от порядка предложений и порядка целей в предложениях.

Процедурное значение зависит от порядка целей и предложений. Поэтому порядок может повлиять на эффективность программы, приводя ее иногда к бесконечным рекурсивным выводам.

Для обеспечения процедурной надежности программ разработаны общие методы предотвращения бесконечных циклов. При решении более сложных задач будем их применять.