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

Волченков Логическое программирование язык пролог 2015

.pdf
Скачиваний:
11
Добавлен:
12.11.2022
Размер:
4.14 Mб
Скачать

Напомним, что спецификация любого предиката (в том числе, и встроенного) на Прологе обозначается так: p/n, где p – имя предиката, n – арность предиката – количество его аргументов. Среди аргументов встроенных предикатов могут быть входные и выходные параметры. Иногда заранее нельзя сказать, будет ли i-й аргумент входным или выходным параметром, но иногда это установлено жёстко. Поэтому для более точной спецификации встроенного (и любого другого тоже) предиката вместо арности n в записи спецификации используется выражение (z1, …, zn), где zi – один из трех знаков: i, o или ?. Первый означает, что данный аргумент – входной параметр, второй – что выходной, а третий – что неопределённый.

Прежде всего, дадим определение арифметического выражения, а затем – определение встроенного предиката is/(?, i).

Арифметическое выражение – это либо простой терм, либо составной терм – совокупность простых термов, связанных знаками арифметических операций. Простой терм – это либо число, либо переменная, либо функциональный терм. Любой терм арифметического выражения должен иметь числовое значение (переменные, входящие в терм, должны быть «означены» числами). Арифметические операции традиционные: +, –, *, /, //, … В табл. 3.1 пред-

ставлен перечень арифметических операций, взятый из справочно-

го файла системы Win Prolog 4200 (фирмы LPA Ltd).

Таблица 3.1

Term

Function

X + Y

adds X to Y

X - Y

subtracts Y from X

- X

returns the negative of X

X * Y

multiplies X by Y

X / Y

divides X by Y

X // Y

performs integer division of X by Y, truncating the result to-

 

wards zero

X mod Y

computes X modulo Y, where result has the same sign as Y

X ^ Y

raises X to the power of Y

abs(X)

computes the absolute value of X

acos(X)

computes the arccosine of X (degrees)

aln(X)

computes the natural antilogarithm of X

alog(X)

computes the common antilogarithm of X

 

41

 

Окончание табл. 3.1

Term

Function

asin(X)

computes the arcsine of X (degrees)

atan(X)

computes the arctangent of X (degrees)

cos(X)

computes the cosine of X (degrees)

fp(X)

computes the fractional part of X (this has the same sign as X)

int(X)

computes the nearest integer less than or equal to X (truncates

 

towards -infinity)

ip(X)

computes the integer part of X (truncated towards zero; this has

 

the same sign as X)

ln(X)

computes the natural logarithm of X

log(X)

computes the common logarithm of X

max(X,Y)

computes the maximum of X and Y (the nearest to +infinity)

min(X,Y)

computes the minimum of X and Y (the nearest to -infinity)

rand(X)

computes a pseudo random floating point number between zero

 

and X

sign(X)

computes the sign of X (-1, 0, or 1 for negative, zero or positive

 

respectively)

sin(X)

computes the sine of X (degrees)

sqrt(X)

computes the square root of X

tan(X)

computes the tangent of X (degrees)

Предикат is/(?, i) всегда записывается в инфиксном виде (без скобок). Например:

29 is (25 mod 7) * 7 + 1, X is tan(Y) + sqrt(Z + 1).

Арифметическое выражение (входной параметр) записывается справа от имени предиката is/2. Слева от имени записывается искомое или проверяемое значение этого арифметического выражения. Переменные Y и Z в данном примере должны быть «означены» – иметь числовые значения. Очевидно, что предикат is/(?, i) по определению служит либо для проверки равенства значения арифметического выражения заданному числу, либо для присваивания переменной значения, равного значению арифметического выражения. Иное использование этого предиката недопустимо.

Сравнивать значения двух арифметических выражений А и В можно с помощью предикатов из табл. 3.2.

Следует отличать представленные в этой таблице отношения равенства и неравенства от следующих отношений между термами:

42

T1 = T2 и T1 \= T2. Эти два отношения означают унифицируемость (сопоставимость) и неунифицируемость (несопоставимость) двух термов Пролога.

Таблица 3.2

Предикат

Смысл предиката

 

 

А =:= В

Значения А и В равны

А =\= В

Значения А и В не равны

А > В

Значение А больше значения В

А < В

Значение А меньше значения В

А >= В

Значение А больше значения В или равно значению В

А =< В

Значение А меньше значения В или равно значению В

Есть и третий вид сравнения термов: T1 = = T2 и T1 \= = T2. В этом случае термы должны быть идентичными (одинаковыми) либо быть неидентичными (неодинаковыми).

Есть несколько предикатов, предназначенных для сравнения термов, а не для сравнения значений арифметических выражений

(табл. 3.3):

Таблица 3.3

Предикат

Смысл предиката

 

 

 

 

Т1 @= Т2

Термы Т1

и Т2

идентичны

Т1 @\= Т2

Термы Т1

и Т2

не идентичны

Т1

@> Т2

Терм Т1

«больше» терма Т2

Т1

@< Т2

Терм Т1

«меньше» терма Т2

Т1

@>= Т2

Терм Т1

«больше» терма Т2 или идентичен терму Т2

Т1

@=< Т2

Терм Т1

«меньше» терма Т2 или идентичен терму Т2

Сравнение («больше» или «меньше») производится в лексикографическом порядке имён термов (Иван @< Иванович). Если имена термов идентичны, то по порядку, слева направо, попарно сравниваются их аргументы. Если число аргументов различно, то при прочих равных условиях более «длинный» терм «больше» бо-

лее «короткого». Например: f(g(a, b), c) @< f(g(a, c), c); ff(a, b) @> f(a, b); [a, b, c] @> [a, b].

2. Встроенные предикаты ввода и вывода

Для ввода терма из входного потока данных используется предикат read/1, а для вывода терма в выходной поток данных – предикат write/1. Для перехода на новую строку и «возврата каретки»

43

используется предикат nl/0 («new line»). Все эти предикаты безвозвратны, то есть не будут повторно вызываться при возврате.

Что понимается под «входным» и «выходным потоками данных»?

По умолчанию, это, соответственно, клавиатура и экран монитора компьютера.

Если же открывается текстовый файл для чтения, то входным потоком становятся записи этого файла: при каждом вызове предиката read/1 читается очередная запись этого файла. Этой записью должен быть синтаксически правильный терм Пролога, заканчивающийся точкой. По умолчанию, последним термом, который всётаки считывается после реальной последней записи файла, является атом end_of_file.

Если же открывается текстовый файл для записи, то выходным потоком становятся записи этого файла: при каждом вызове предиката write/1 терм записывается в этот файл. Чтобы в дальнейшем этот файл мог стать файлом для чтения, в конец формируемой записи необходимо вставить точку и перейти к новой записи, то есть выполнить две процедуры:

write(’.’), nl.

Открытие файла для чтения обеспечивает предикат see/1, а открытие файла для записи – предикат tell/1. Единственным аргументом каждого из этих предикатов является путь к данному файлу в файловой структуре компьютера. Закрытие файлов обеспечивается, соответственно, предикатами seen/0 и told/0.

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

Можно читать из входного потока и писать в выходной поток не терм, а любой символ. Для этого используются предикаты get0/1 и put/1. Аргументом каждого из этих двух предикатов является код вводимого или выводимого символа. Оба этих предиката безвозвратного действия.

3. Встроенные предикаты управления

Можно эффективно управлять ходом логического вывода, используя специальные встроенные предикаты, например, предикат «отсечения» («cut»). В утверждениях Пролога этот предикат изо-

44

a :– b1, …, bn, !, c1, …, cm.

бражается с помощью восклицательного знака: !/0. Семантика этого предиката такова.

Предикат «отсечения» выполняется всегда. Решающим является побочное действие этого предиката: он устанавливает запрет на возвраты к подцелям, находящимся левее его в записи утверждения, в котором этот предикат находится. Рассмотрим, например, следующее утверждение:

Если после выполнения «отсечения» цель cj (j = 1, …, m) не может быть выполнена (дала «отказ»), то возврат может быть осуществлён к любой из целей ci (i<j). Но возврат не может быть осуществлён ни к одной из целей bk (k = 1, …, n), а также к цели a, стоящей в левой части правила.

Главной областью применения «отсечения» является повышение эффективности работы переборных алгоритмов путем удаления программистом заведомо бесперспективных или заведомо тупиковых частей графа логического вывода.

Пример 3.1. Рассмотрим фрагмент базы данных Пролога:

собрался_в_кино :– хороший_фильм(X), посмотрю(X). посмотрю(X) :– нравится(A, X), в_программе(X, B).

Допустим, многочисленные факты базы данных, определяющие предикат хороший_фильм/1, содержат информацию, взятую из энциклопедии кино, в которую включены статьи о сотнях и даже тысячах «хороших фильмов». Допустим также, что число фактов нравится(A, X) тоже велико. В них содержатся сведения, полученные у моих друзей по телефону (в моей записной книжке очень много телефонов моих друзей). Аргумент A – имя моего друга, аргумент X – название фильма. И, наконец, предположим, что многочисленные факты в_программе(X, B) извлечены из газеты «Досуг в Москве». (Аргумент X – название кинофильма, аргумент B – название кинотеатра.)

Рассмотрим логический вывод для целевого утверждения:

?– собрался_в_кино.

После 1-го шага получим

?– хороший_фильм(X), посмотрю(X).

45

Берём первый по списку хороший фильм, например «Амаркорд» Феллини. После 2-го шага логического вывода получим

?– посмотрю(Амаркорд).

После 3-го шага логического вывода (после применения второго из приведённых выше правил) получим

?– нравится(A, Амаркорд), в_программе( Амаркорд, B).

Допустим, фильм «Амаркорд» нравится всем моим друзьям, но не демонстрируется ни в одном московском кинотеатре. Пролог по возврату будет делать множество лишних шагов – как бы обзванивать одного за другим всех моих друзей, чтобы после очередного звонка каждый раз заново безуспешно просматривать большой список кинотеатров.

Казалось бы, есть выход: поменять местами подцели в правой части второго правила:

посмотрю(X) :– в_программе(X, B), нравится(A, X).

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

А правильное решение заключается в том, чтобы поместить предикат «отсечения» между указанными подцелями:

посмотрю(X) :– нравится(A, X), !, в_программе(X, B).

Если фильм нравится хотя бы одному моему другу, то «отсечение» заблокирует возвраты к «записной книжке» и, если фильм нигде не демонстрируется, то сразу, без возвратов, будет взят следующий факт из «энциклопедического» списка хороший_фильм(X). И снова Пролог начнёт «звонить моим друзьям». Очевидно, что предикат отсечения обеспечил существенное уменьшение числа бесполезных просмотров базы данных.

Ещё один предикат управления – это заведомо невыполнимый предикат fail. Его используют для создания «искусственного тупи-

46

ка» в ходе логического вывода, чтобы инициировать возврат. Для этого следует использовать именно этот предикат, а не какуюнибудь «абракадабру» – предикат, не определённый в базе данных, так как это избавит систему от необходимости поиска «абракадабры» в базе данных.

К предикатам управления следует отнести также предикат call/1. Его аргументом является переменная, значением которой является цель – предикат. Предикат call отправляет свой аргумент на выполнение, а выполнимость его аргумента означает выполнимость и самого предиката call. Данный предикат необходим, так как переменную в качестве подцели утверждения использовать нельзя.

Пример 3.2. Все три перечисленных выше предиката управления можно использовать в определении предиката not/1 – логического отрицания (реально, – это встроенный предикат):

not(P) :– call(P), !, fail. not(P).

Пусть в базе данных есть всего два факта о «хороших фильмах»:

хороший_фильм(КрестныйОтец). хороший_фильм(Матрица).

Рассмотрим запрос:

?– not(хороший_фильм(Матрица)).

Сработает 1-е правило определения. После успешного вызова подцели call(P) сработает «отсечение» (!) и «тупик» (fail). Возврат к цели not(P) – использованию 2-го правила определения – будет заблокирован. Ответ на запрос отрицательный.

Рассмотрим запрос:

? – not(хороший_фильм(ФоррестГамп)).

После безуспешного вызова подцели call(P) до «отсечения» дело не дойдёт, поэтому оно не будет выполнено. Возврат к цели not(P) – использованию 2-го правила определения – не будет заблокирован. Ответ на запрос будет положительным. Хорошо ли это

– по большому счёту? Ох уж эти системы с ограниченным миром! Ещё один пример использования предиката «отсечения» – дос-

тижение эксклюзивности при применении альтернативных правил.

47

Пример 3.3. Рассмотрим следующую программу:

Код 3.1

pension :– write(’Сколько Вам лет?’), nl, read(X), answer(X). answer(X) :– write(’Вы мужчина (м) или женщина (ж)?’), nl,

read(Y), answer(X, Y). answer(X, Y) :– Y == ж, условие_ж(X). answer(X, Y) :– Y == м, условие_м(X). усл_ж(X) :- X >= 55,

write(’Вы, сударыня, уже пенсионерка.’), nl.

усл_ж(X) :- X < 55,

write(’Вы, девушка, ещё не пенсионерка.’), nl. усл_м(X) :- X >= 60, write(’Вы, сударь, уже пенсионер.’), nl.

усл_м(X) :- X < 60, write(’Вы, юноша, ещё не пенсионер.’), nl.

Программа работает следующим образом («фотография» консоли):

| ?- pension.

Сколько Вам лет? |: 57.

Вы мужчина (м) или женщина (ж)?

|: ж.

Вы, сударыня, уже пенсионерка. yes

В этой программе делается лишняя работа. В 4-м правиле проверяется, мужчина ли пользователь, хотя невыполнение предыдущего правила говорит о том, что он не женщина. В 6-м и 8-м правилах проверяется, не меньше ли значение переменной X заданных чисел, хотя невыполнение предыдущих правил (5-го и 7-го) делает излишней эту проверку.

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

дуры, в которой может возникнуть «внешний» возврат. Тогда по возврату может ошибочно сработать 4-е правило вместо 3-го, хотя пользователь – женщина. И может также ошибочно сработать 6-е правило вместо 5-го или 8-е правило вместо 7-го.

Решение этой проблемы во включении в правую часть указанных правил отсечения:

48

answer(X, Y) :– Y == ж, !, условие_ж(X). answer(X, Y) :– !, условие_м(X).

усл_ж(X) :- X >= 55, !, write(’Вы, сударыня, уже пенсионерка.’), nl.

усл_ж(X) :- !, write(’Вы, девушка, ещё не пенсионерка.’), nl. усл_м(X) :- X >= 60, !, write(’Вы, сударь, уже пенсионер.’), nl. усл_м(X) :- !, write(’Вы, юноша, ещё не пенсионер.’), nl.

Эксклюзивность каждого из этих правил соблюдена! И ещё одна область применения отсечения.

Предикат отсечения позволяет реализовать характерные для традиционных языков программирования условные конструкции (if then else), а также циклические конструкции (например, циклы с условием).

Рассмотрим программу на Прологе:

p :– a1, !, b1. p :– a2, !, b2.

p :– an-1, !, bn-1. p :– bn.

Эта программа реализует функцию на языке Бейсик:

Function p(…) As Boolean p = False

If a1 Then

p = b1 ElseIf a2 Then

p = b2

ElseIf an-1 Then

p = bn-1 Else

p = bn End If

End Function

49

Программирование повторений

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

Метод обхода (без возвратов)

Рекурсивно выбирается и удаляется элемент какого-нибудь множества (для его представления можно использовать список) и над этим элементом производится какое-нибудь действие. Цикл заканчивается, когда множество становится пустым.

Пример 3.4. Печатаются «в столбик» все элементы списка:

выдать_по_очереди([]).

выдать_по_очереди([H|T]) :– write(H), nl, выдать_по_очереди(T).

Недостаток метода: рекурсивные вызовы «забивают» так называемый «резолюционный» стек; его очищали бы возвраты, но здесь их нет.

Метод поиска (возвратный метод)

Элементы какого-нибудь множества (для его представления можно использовать список) выбираются с помощью возвратной процедуры, например с помощью предиката member/2, о которой будет идти речь в следующей лекции. Над выбранным элементом производится какое-нибудь действие, после чего реализуется «искусственный» возврат с помощью предиката fail.

Пример 3.5. Печатаются «в столбик» все элементы списка:

выдать_по_очереди(List) :– member(X, List), write(X), nl, fail.

выдать_по_очереди(_).

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

CAF-метод (метод «cut and fail»)

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

50

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]