Добавил:
natribu.org Все что нашел в интернете скидываю сюда Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Точно Не проект 2 / Не books / Источник_1

.pdf
Скачиваний:
10
Добавлен:
01.02.2024
Размер:
20.67 Mб
Скачать
not(отец(X, Y))

330

Глава 6

 

 

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

отец('Иван', 'Сергей'). отец('Иван', 'Ольга' ). отец('Петр', 'Николай').

не_отец(X, Y): – отец(X, Y), !, fail; true.

При попытке ответа на вопрос

?- не_отец ('Петр', 'Татьяна').

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

Многие реализации Пролога содержат встроенный предикат not. Тогда предикат не_отец(X, Y) можно определить следующим образом:

не_отец(X, Y): – not(отец(X, Y)).

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

6.8. Метаусловия

Пролог допускает использование переменных не только в качестве аргументов предикатов, но и вместо условий (предикатов). Такие условия

(переменные) называют метаусловиями (метапеременными). При выпол-

нении метаусловия переменная, представляющая такое метаусловие, должна быть конкретизирована. В приведенном ниже правиле

p(A, B, C): – A, B; C.

переменные А, В, С представляют метаусловия. При вызове предиката р(А, В, С) на место аргументов должны подставляться конкретные условия. Используя метаусловия, определим предикат not:

not(P): – P, !, fail; true.

Язык Пролог

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. примеру, где представлена база данных, состоящая из фактов:

отец(Отец, Ребенок).

332

Глава 6

 

 

Определим предикат дети(Отец), обеспечивающий вывод на экран имен всех детей конкретного отца. В этом случае программа должна последовательно просматривать все факты базы данных при конкретном значении переменной Отец и выводить на экран соответствующее значение переменной Ребенок. Для того чтобы заставить программу перебирать факты базы данных, организуем состояние искусственной неудачи (fail). Это приведет к тому, что пролог-система выполнит возврат к иному варианту (если такой имеется) унификации предиката отец(Отец, Ребенок). Вывод на экран имени ребенка будем выполнять с помощью встроенного предиката write(X), где Х – переменная, значение которой отображается на экране. Тогда для предиката дети(Отец) можно записать следующее определение:

дети(Отец): – отец(Отец, Ребенок),

write(Ребенок), fail; true.

На рисунке 6.6 изображено дерево вывода целевого утверждения

? – дети('Иван').

Имеются два варианта унификации предиката отец('Иван',Ребенок). Пролог-система проверяет оба варианта. После вывода на экран первого значения переменной Ребенок, пролог-система встречает условие fail и переходит к варианту Ребенок='Анна'. В конце второго пути пролог-система опять встречает условие fail и переходит к третьему варианту, который завершается условием true.

Рисунок 6.6 – Дерево вывода целевого утверждения дети('Иван')

Язык Пролог

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).

334

Глава 6

 

 

Рисунок 6.7 – Создание искусственных точек выбора

Таким предикатом является предикат repeat, встроенный во многие реализации языка Пролог:

repeat: – true; repeat.

Дерево вывода предиката repeat изображено на рисунке 6.8. Каждый вызов предиката repeat порождает новую ветвь вычислений.

Рисунок 6.8 – Дерево вывода предиката repeat

Воспользуемся предикатом repeat и напишем циклическую программу, вычисляющую значение квадратного корня очередного числа, введенного с клавиатуры. Если вместо числа с клавиатуры будет введен атом 'стоп', то вычисления прекращаются:

цикл: –

repeat,nl,write('число?'), read(X), (X='стоп', !;

квадратный_корень(X, Y), write('корень='), write(Y),fail).

Язык Пролог

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.Встроенные предикаты

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

336

Глава 6

 

 

6.10.1. Предикаты ввода-вывода

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

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

read(X) – вызывает чтение очередного терма из входного потока и сопоставление его с X, вводимые термы должны заканчиваться точкой, в конце файла X конкретизируется атомом end_of_file;

write (X) выводит терм X в текущий выходной поток; nl – переход на новую строку;

tab(N) – выводит в выходной поток N-пробелов; put(X) – выводит символ с ASCII кодом X на терминал;

get0(X) – считывает один символ с клавиатуры и сопоставляет его Х; get(X) – сопоставляет Х с первым символом, отличным от пробела; display(X) – выводит терм Х в стандартной скобочной записи в теку-

щий выходной поток.

Таблица 6.2– Предикаты управления потоками

Входной поток

Выходной поток

Интерпретация предиката

see(<имя файла>)

tell(<имя файла>)

Определяет в качестве теку-

щего входного или выходного

 

 

потока соответствующий

 

 

файл.

seeing(<имя файла>)

telling(<имя файла>)

Отождествляет имя файла с

 

текущим входным или выход-

 

 

 

ным потоком.

 

seen

told

Закрывает текущий входной

или выходной поток и опять

 

 

связывает с текущим входным

 

 

или выходным потоком поль-

 

 

зовательский терминал.

Файлы Пролога являются текстовыми и обрабатываются последовательно. Типовая последовательность вызова встроенных предикатов при выполнении ввода из файла является следующей:

Язык Пролог

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).

338

Глава 6

 

 

Этот предикат повторяет запрос фамилии, пока не будет введен атом ‘стоп’. Вводимые фамилии должны заканчиваться точкой.

Пример 3. Определим предикат, вычисляющий значения арифметических выражений, вводимых с клавиатуры:

калькулятор:–

write(‘Введитевыражение’), nl, read(X),

(X= ’стоп ’, ! ;

Y is X, write(Y), nl,

калькулятор).

В этом случае в ответ на вопрос

?-калькулятор.

пролог-система предлагает пользователю ввести арифметическое выражение. После ввода выражения, например,

107+(302*3)/4.

оно будет сопоставлено переменной Х и вычислено с помощью предиката is. В результате на экран будет выведено значение 333.5.

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

% основной предикат обработка_файлов(Файл1,Файл2):-

see(Файл1),

% открытие файлов

tell(Файл2),

 

чтение_обработка_запись,

seen, told.

% закрытие файлов

%предикат, выполняющий ввод выражений,

%вычисления и запись результатов

чтение_обработка_запись:– read( Выражение ),

обработка(Выражение, Результат), запись(Выражение, Результат),

Выражение \= = end_of_file,

чтение_обработка_запись. чтение_обработка_запись:–!.

% предикат, вычисляющий значения выражения обработка(end_of_file,end_of_file):–!.

Язык Пролог

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, и указанная проверка успешно завершается,

Соседние файлы в папке Не books