Точно Не проект 2 / Не books / Источник_1
.pdfЯзык Пролог |
331 |
|
|
В данном определении Р – метаусловие. Подстановка вместо Р конкретного утверждения обеспечивает успех, если метаусловие Р не достижимо. Предикат not(P) реализует отрицание в соответствии с предположением замкнутости мира.
Введение метаусловий позволяет реализовать управляющую конструкцию if_then_else, характерную для традиционных языков программирования:
if(Если, То, Иначе): – Если, !, То; Иначе.
Здесь Если, То, Иначе – метаусловия. Предикат if удобно использовать при программировании взаимоисключающих версий. Например, переопределим предикат abs(X, Y):
abs(X, Y): – if(X>=0, Y is X, Y is -X).
В этом определении метаусловие Если задано отношением Х>=0. Если данное отношение справедливо, то выполняется условие Y is X, иначе условие Y is -X.
Подобным образом можно использовать предикат if при выборе максимального из двух чисел:
max(X, Y, Z): – if(X>=Y, Z is X, Z is Y).
Отметим, что Пролог содержит встроенный предикат call(P), который обеспечивает оценивание (доказательство) условия Р. Вызов предиката call(P) эквивалентен использованию метаусловия. Например, предикат not можно определить так:
not(P): – call(P), !, fail; true.
6.9. Организация циклов
При решении практических задач часто существует необходимость в повторении некоторых операций. В процедурных языках программирования для этого используются операторы циклов или рекурсия. Возможность организации циклических вычислений с помощью рекурсии рассматривалась выше. Здесь рассмотрим организацию циклов в Прологе с использование механизма возврата.
Вернемся к рассмотренному в § 6.2. примеру, где представлена база данных, состоящая из фактов:
отец(Отец, Ребенок).
Язык Пролог |
333 |
|
|
Схема выполнения пролог-программы, основанная на создании искусственной неудачи, называется циклом с возвратом.
Рассмотренный предикат дети(Отец) можно обобщить. Определим предикат, который выполняет поиск всех решений некоторого известного метаусловия:
все_решения(Условие, Х): –
Условие, write(X), fail; true.
Здесь переменная Х является также аргументом метаусловия Условие. Все решения, соответствующие предикату дети(Отец), можно полу-
чить с помощью вызова:
все_решения(отец(Отец, Х), Х).
С помощью рассмотренной схемы организации циклов легко организовывать вычисления, соответствующие вложенным циклам процедурных языков. Пусть требуется вывести на экран общие элементы списков L1 и L2. Иными словами, для каждого элемента списка L1 необходимо найти соответствующие элементы списка L2. Данная задача может быть решена следующим образом:
общие_элементы(L1, L2): –
элемент(E, L1), элемент(Е, L2),write(E), fail; true.
В рассмотренных примерах пролог-система возвращалась к точкам выбора, порождаемым предикатами: отец(Отец, Ребенок), элемент(E, L1), элемент(E, L2). Предикаты, создающие точки выбора, называют недетерминированными. Иногда необходимо осуществлять повторное выполнение предикатов, не создающих точек выбора (детерминированных). Для этого можно организовать цикл с возвратом, создав искусственные точки выбора.
Пусть требуется последовательно вводить с клавиатуры числа и вычислять для каждого числа квадратный корень. В нашем распоряжении имеются два предиката: встроенный предикат read(X) , обеспечивающий чтение значений с клавиатуры (подробнее предикаты ввода рассматриваются в следующем параграфе), и предикат, вычисляющий квадратный корень квадратный_корень(X, Y). Для того чтобы организовать повторное выполнение указанных предикатов, необходимо перед предикатом read(X) поместить предикат, который будет создавать бесконечное число точек выбора и всегда выполняться успешно (рисунок 6.7).
Язык Пролог |
335 |
|
|
Здесь встроенный предикат nl обеспечивает перевод курсора на новую строку.
Предикат repeat можно переопределить так, чтобы он обеспечивал построение циклов с заданным числом повторений. В этом случае он должен порождать конечное множество ветвей вычислений, а не бесконечное. Для этого введём предикат repeat с одним аргументом N, фиксирующим количество циклов:
repeat(1): – !. repeat(N): – true;
M is N-1, repeat(M).
Рассмотренные выше циклические программы вычисления квадратного корня можно было выполнить, используя рекурсивную программу:
цикл: –
nl, write('число?'), read(X), (X='стоп', !;
квадратный_корень(X, Y), write('корень='),write(Y),цикл).
Сравнив два возможных варианта циклических вычислений, отметим, что цикл с возвратом используется ограниченно. Это связано с тем, что при каждом возврате конкретизации переменных теряются. Поэтому передача промежуточных результатов от одной ветви вычислений к другой невозможна. Циклы с возвратом обычно применяются в тех случаях, когда промежуточные результаты выводятся на экран или фиксируются в базе данных с помощью специальных встроенных предикатов, рассматриваемых ниже.
Рекурсивный вариант организации циклических вычислений, по сравнению с циклом repeat-fail, требует бóльшего объема стековой памяти, так как на каждом шаге рекурсии все конкретизации переменных сохраняются. Исходя из этих соображений, цикл repeat-fail иногда предпочитают рекурсии.
6.10.Встроенные предикаты
Впараграфе описываются встроенные предикаты Пролога, обеспечивающие выполнение операций ввода-вывода, обработки термов и обновления базы данных. Встроенные предикаты реализуются либо средствами самого Пролога, либо средствами языков более низкого уровня, например, ассемблера. Встроенные предикаты не могут быть изменены пользователем.
Язык Пролог |
337 |
|
|
…, |
|
see(‘имя файла’), |
% открытие файла |
read(X), |
% чтение терма Х |
…, |
% обработка |
seen. |
% закрытие файла |
Аналогично строится работа с выходным потоком:
…, |
|
tell(‘имя файла’), |
% открытие файла |
write(X), |
% запись терма Х |
…, |
|
told. |
% закрытие файла |
Предикаты seen и told закрывают соответственно входные и выходные файлы и опять связывают с текущим входным или выходным потоком терминал пользователя.
Рассмотрим применение предикатов ввода-вывода на примерах.
Пример 1. Вывод списка на экран.
вывод _списка([ ]).
вывод _списка([H L]):– write(H),nl,вывод _списка(L).
В этом случае список [‘мяч ‘, ‘заяц’, ’мишка’] будет отображен на экране в виде:
мяч
заяц
мишка
Пример 2. Обработка запроса к базе данных. Пусть имеется база данных, содержащая факты:
адрес (Фамилия, Адрес).
Определим предикат, запрашивающий фамилию и осуществляющий вывод адреса:
вывод _адреса:–
repeat, write (‘Фамилия?’), nl, read (X),
(Х= ‘стоп’, ! ;
адрес( Х, Адрес), write(‘Адрес:’), write( Адрес), nl, fail).
Язык Пролог |
339 |
|
|
обработка(Выражение, Результат):– Результат is Выражение, ! .
% запись результатов в файл запись(end_of_file,_):–!.
запись(Выражение, Результат):– write(Выражение), write (‘=’), write(Результат), write (‘.’), nl, !.
Для запуска программы необходимо вызвать основной предикат, указать имена входных и выходных файлов. Например,
?–обработка_файлов(‘f1.txt’, ‘f2.txt’).
Здесь предполагается, что файлы f1.txt и f2.txt размещаются в текущем каталоге. Если есть необходимость в размещении этих файлов в иных каталогах, то необходимо указывать полные пути доступа к файлам.
Входной файл f1.txt может быть подготовлен с помощью любого текстового редактора. Пусть файл f1.txt содержит следующие выражения:
1+2*3.
7-27/3. 1+sin(1.5417).
Тогда результирующий файл будет содержать следующие записи:
1+2*3=7. 7-27/3= -2.0.
1+sin(1.5417)=1.99999982.
В рассмотренном примере процедура, выполняющая чтение, обработку и запись результатов, определена рекурсивно. При большом числе записей во входном файле это может привести к исчерпанию стековой памяти. В этом случае можно воспользоваться циклом repeat–fail:
чтение_обработка_запись(Файл1, Файл2):– repeat,
read(Выражение), обработка(Выражение, Результат), запись(Выражение, Результат),
Выражение == end_of_file,!.
Здесь состояние неудачи (fail) возникает всякий раз при проверке равенства терма, представленного переменной Выражение, и атома end_of_file. Когда входной файл будет исчерпан, переменная Выражение получает значение end_of_file, и указанная проверка успешно завершается,