Язык логического программирования ПРОЛОГ

Язык ПРОЛОГ предназначен для представления и использования знаний в различных предметных областях. Математическую основу языка составляет исчисление предикатов первого порядка (ИППП), при этом объекты предметной области, их свойства и связи представляются конъюнкцией правильно построение формул специального вида, называемых дизъюнктами Хорна. Для решения задачи получения новой информации об отношениях предметной области, формулируемой как задача доказательства теоремы, в интерпретаторе системы программирования ПРОЛОГ реализован метод резолюции.

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

P0 | ~P1 | ~P2 | ... | ~Pn,
где n>=0, Pi - атомарная формула (предикат), все переменные связаны не указанными явно кванторами всеобщности, областью действия которых служит вся формула. Атомарная формула, входящая в дизъюнкт с отрицанием, называется отрицательным литералом, а формула без отрицания - положительном литералом.
Эквивалентной, но более удобной для человека, является импликативная форма записи дизъюнктов Хорна:
(P1 & P2 & ... & Pn) -> P0,
которую, с учётом правила Modus Ponens, можно трактовать как "Если ..., то ...".
Различные типы дизъюнктов Хорна имеют специальные названия в языке Пролог, что иллюстрирует следующая таблица.
Дизъюнкт Хорна Импликативная форма Утверждение Пролога
P0 P0 Факт
P0 | ~P1 | ~P2 | ... | ~Pn (P1 & P2 & ... & Pn) -> P0 Правило
~P1 | ~P2 | ... | ~Pn ~(P1 & P2 & ... & Pn) Запрос (цель)

Программа на языке ПРОЛОГ состоит из утверждений (предложений, дизъюнктов Хорна), составляющих базу фактов и базу правил, к которым допустимо обращение с запросами. Запросы называются также целевыми утверждениями или просто целями.
Термин "программа" мы применяем к ПРОЛОГу по "традиции", т.к. ПРОЛОГ - декларативный язык!

Большинство существующих систем программирования ПРОЛОГ являются комбинированными в том смысле, что допускают как интерпретацию программ, так и их компиляцию.

Основные синтаксические конструкции языка ПРОЛОГ

Утверждения (предложения) языка ПРОЛОГ конструируются из термов.
Терм - это либо константа,
либо переменная,
либо структура.
Примечание. Отметим, что понятие терма в ПРОЛОГе отличается от этого понятия в ИППП.

Константа - это либо атом,
либо число.

Атомы бывают трех типов:

  1. последовательность букв, цифр и знака "подчёркивание", обязательно начинающаяся со строчной буквы (маленькой буквы);
  2. последовательность спецзнаков ":-", "?-", "=", ">=", "--" и др.;
  3. заключённая в апострофы (одинарные кавычки) последовательность любых символов.
Примеры атомов: abcD, a_gear, '17-256', xyz100.
Любая система программирования ПРОЛОГ обеспечивает работу с целыми числами и большинство - с действительными.
Константы представляют собой самоопределённые термы, т.е. их "значением" является их графическое представление (последовательность символов или число).
Константы в Прологе используются для обозначения (именования) конкретных объектов предметной области (ср. с предметными константами ИППП) и конкретных отношений между ними (ср. с предикатными и функциональными буквами ИППП).

Переменная - последовательность букв, цифр и знака "_", обязательно начинающаяся с прописной (большой) буквы или знака "_".
Переменные ПРОЛОГа полностью аналогичны предметным переменным ИППП. В утверждениях языка ПРОЛОГ переменные связаны не указываемыми явно кванторами общности.
Среди переменных выделена переменная "_" (один знак подчёркивания), называемая анонимной. Она используется в ситуациях, когда нас не интересует её значение.

Структура представляется на языке ПРОЛОГ с помощью указания её функтора и компонент в следующем виде:

функтор(компонента-1, ...., компонента-N)
,где в качестве функтора должен выступать атом, а компонентой может быть любой терм (в том числе и структура).
Структуры языка ПРОЛОГ используются для представления: Использование структур для представления сложных данных (в т.ч. списков) и операций будет рассмотрено ниже.

Факты и запросы

Базу фактов в программе па языке ПРОЛОГ составляют утверждения, описывающее факты предметной области в виде структур, функторами которых являются атомы - имена отношений (предикатные буквы), а компонентами - предметные константы.
Пример. Факты из рассматриваемой в пособии предметной области конструирования башен могут быть представлены в виде утверждений языка ПРОЛОГ следующим образом:


roof(prizm, 4).			/* 1 */
roof(plane, 1).			/* 2 */
block(cyl, 5).			/* 3 */
block(cyl, 3).			/* 4 */
block(par, 4).			/* 5 */
block(par, 2).			/* 6 */
Здесь каждый факт представляет собой элементарную формулу (предикат) ИППП и является дизъюнктом Хорна, состоящим из одного (положительного) литерала.
Отметим, что согласно синтаксису языка ПРОЛОГ каждое утверждение должно заканчиваться точкой, а символы, заключенные между /* и */, считаются комментарием. В нашем примере в качестве комментариев используются номера утверждении.
Подчеркнем, что при описании фактов переменные не используются.

Система программирования ПРОЛОГ позволяет ставить запросы к базе фактов, касающиеся непосредственного ее содержимого. Простейшим видом запроса является запрос на проверку наличия в базе какого-либо факта, имеющий вид:

?-структура.
Здесь структура описывает необходимый факт. Пытаясь ответить на такой запрос, интерпретатор ПРОЛОГа ищет факт, имеющий тот же функтор (предикатную букву), что и содержащийся в запросе, и далее попарно сопоставляет компоненты двух структур, выдавая ответ Yes или No в зависимости от результата сопоставления.

Пример. Ниже представлены запросы к нашей базе фактов (левая колонка символов) и ответы на них (правая колонка).


?-block(cyl, 3).	Yes
?-roof(plane, 2).	No
?-block(prizm, 4).	No
?-roof(plane).		No
?-base(strip, 1).	No
Примечание. С точки зрения метода резолюции, реализованного в интерпретаторе ПРОЛОГа, поиск ответа на запрос - это применение правила резолюции к отрицанию целевого утверждения (теоремы) и факту (представляющему собой простейший вид дизъюнкта Хорна) с целью вывода пустого дизъюнкта.

Более полезными с точки зрения пользователя и более сложными с точки зрения формирования ответа являются запросы, содержащие переменные в качестве компонент структуры. Поиск ответа на такой запрос связан с использованием механизма конкретизации переменных значениями в процессе сопоставления структур, представляющих собой запрос и факт. В рамках языка ПРОЛОГ считается, что переменная, как компонента структуры, всегда сопоставима с компонентой (любым термом) другой структуры, находящейся в той же самой позиции, что и переменная (естественно, при условии, что функторы структур совпадают). Если переменная сопоставляется с константой или структурой, то значением переменной становится эта константа или структура. Случай сопоставления двух переменных (называемый связыванием) будет рассмотрен ниже. Ответом на запрос, содержащий переменные, служат значения этих переменных.
Пример. Ниже представлен фрагмент диалога пользователя с системой программирования ПРОЛОГ, касающийся фактов об элементах строительных конструкций.


?-roof(prizm, H).	H = 4
?-block(F, 3).		F = cyl
?-block(F, H).		F = cyl, H = 5
;			F = cyl, H = 3
;			F = par, H = 4
;			F = par, H = 2
;			No
Здесь слева приведены запросы пользователя, а справа - ответы на них. Первый запрос позволяет узнать высоту крыши призматической формы, второй - выяснить, какова форма блока, имеющего высоту 3. Третий запрос предназначен для вывода из базы фактов всей имеющейся информации о блоках.

Рассмотрим подробно процесс обработки интерпретатором последнего запроса в примере. Получив его, интерпретатор просматривает базу фактов, начиная с самого первого утверждения, пока не удастся сопоставить целевое утверждение с третьим фактом. Здесь интерпретатор выдает значения конкретизированных переменных запроса, устанавливает специальный маркер (указатель) на утверждение 3 и переходит в состояние ожидания реакции пользователя. Пользователь, желая продолжить просмотр базы фактов, вводит специальный символ ";" (точка с запятой), сигнализируя интерпретатору о продолжении поиска. Интерпретатор а) расконкретизирует переменные, делая их опять неопределенными, и б) продолжает сопоставление, начиная с утверждения, непосредственно следующего за отмеченным маркером.

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

Арифметика

Для описания арифметических операций в языке ПРОЛОГ используются структуры, функторами которых выступают знаки арифметических действий, а компонентами - термы, являющиеся операндами. В качестве операндов могут использоваться числа, переменные и структуры. Последние, в свою очередь, должны представлять собой арифметические выражения. С точки зрения ИППП знаки арифметических операций в таких структурах выступают в качестве функциональных букв.
Однако запись арифметических выражений в форме структур (в префиксной форме) для человека непривычна, поэтому синтаксис языка ПРОЛОГ допускает для них и альтернативную - инфиксную - форму. Ниже даны примеры записи арифметических выражений в обеих формах.
+(2,2)=2+2
-(Y,X)=Y-X
*(+(X,2),-(16,Y))=(X+2)*(16-Y)
/(*(X,Y),Z)=(X*Y)/Z
mod(X,3)=X mod 3

Сравнение

Встроенные предикаты, осуществляющие сравнение термов, также имеют две формы записи, однако в программах на языке ПРОЛОГ чаще используется более привычная - инфиксная. Этими предикатами являются: равно (=), не равно (\=), больше (>), меньше (<), больше-равно (>=), меньше-равно (=<). Четыре последние могут быть использованы для сравнения только чисел или переменных, конкретизированных числами. Два первых годятся для сравнения любых термов. Ниже даются примеры использования этих предикатов в запросах.


?- 4 > 2.		Yes
?- abc = 'abc'.		Yes
?- cad \= cam.		Yes
?- x = X.		X = x
?- Y = cat.		Y = cat
?- X = Y.		Yes

Примеры с предикатом равно иллюстрируют важное его свойство - конкретизацию переменной, являющейся одним из его аргументов, значением другого аргумента, что дает положительный результат проверка. Ответом на запрос X = Y будет Yes, поскольку предикат "равно" в случае, когда оба его аргумента являются неконкретизированными переменными, связывает их, делая синонимами, указывающими на одно и то же значение (пока неопределённое).

Важно чётко представлять себе работу предиката "равно". При согласовании с базой данных цели вида X=Y действуют следующие правила:

Пример. Сопоставление двух структур.

?- a(B,c,D,e(f,H)) = a(b,C,x(y,z),e(F,h)).
        B=b, C=c, D=x(y,z), F=f, H=h
Примеры для самостоятельной работы:

?- point(X,Y,15) = point(25,Y1,Z1).
?- bEAr = 'bEAr'.
?- f(X,X) = f(a,b).
?- f(X,a(b,c)) = f(Z,a(Z,c)).

Расширенной версией предиката "=" является встроенный предикат "is". Дело в том, что перед собственно сравнением двух своих аргументов этот предикат осуществляет вычисление своего правого аргумента (при условии, конечно, что этот аргумент можно интерпретировать как арифметическое выражение).

Примеры.


?- X = +(2,2).		X = 2+2
?- X is 2+2.		X = 4
?- 2+2 = +(2,2).	Yes
?- 2+2 is +(2,2).	No
Примечание. Обращаем внимание, что в языке ПРОЛОГ для представления как предикатных, так и функциональных букв ИППП используются атомы, а маркером, помечающим функциональную букву, как раз и служит встроенный предикат "is".

Сложные запросы

Введенные элементы языка ПРОЛОГ позволяют рассмотреть более сложные типы запросов к базе фактов. В общем виде запрос (целевое утверждение) формулируется в следующем виде:

?-<структура-1>, ..., <структура-N>.

Здесь каждая структура представляет собой предикат, возможно содержащий переменные. Причем областью действия переменной является всё утверждение в целом, т.е одна и та же переменная в пределах утверждения означает один и тот же объект. Символ "," (запятая) между предикатами трактуется как логическая связка И, т.е. запрос необходимо рассматривать как требование на поиск в базе фактов информации, удовлетворяющей одновременно всем предикатам целевого утверждения. Предикаты, объединенные связкой И в таком запросе, называются подцелями (имея в виду весь запрос целью).

Пример. Пусть необходимо получить из базы фактов информацию о наличии строительных блоков высотой более 2 и менее 5. Тогда соответствующий диалог с интерпретатором ПРОЛОГа будет иметь следующий вид.


?- block(F, H), H>2, H<5.		F = cyl, H = 3
;					F = par, H = 4
;					No

Получив запрос (целевое утверждение), состоящий из нескольких предикатов, интерпретатор пытается выполнить его, используя линейную по входу стратегию метода резолюции, реализуя следующую схему вычислений, поясняемую последним рассмотренным примером. Выбирается первый в последовательности запроса предикат и делается попытка (если этот предикат не встроенный) согласовать его с базой фактов, для чего выполняется сопоставление этого предиката последовательно со всеми утверждениями базы до тех пор, пока оно не даст положительного результата. Если этого не происходит, интерпретатор выдает в качестве ответа No. В ходе согласования возможна конкретизация переменных значениями. В нашем примере на этом этапе будет выполнено согласование предиката block(F,H) с третьим утверждением базы фактов, при этом переменная F получит значение cyl, переменная Н - 5, а маркер будет установлен на утверждении 3.
Удовлетворив один предикат (подцель) запроса, интерпретатор переходит к соседнему справа (у нас это H>2). Этот предикат - встроенный, для его проверки нет необходимости обращаться к базе фактов, поскольку интерпретатор в состоянии сам установить его истинность (5>2). Далее следует проверка (попытка "согласовать") последнего предиката в запросе, которая заканчивается неудачей. Здесь включается в работу механизм бэктрекинга (возврата), который заставляет интерпретатор, передвигаясь по предикатам целевого утверждения справа налево, вновь согласовывать эти предикаты, но уже на новых утверждениях базы фактов. Если попытка пересогласовать какой-либо предикат (подцель) интерпретатору удается, то он продолжает рассмотрение подцелей от данной в обычном порядке (слева направо). В нашем примере бэктрекинг достигнет предиката block(F,H), это приведет к расконкретизации переменных F и H и поиску нового утверждения, согласующегося с ним, начиная со следующего после отмеченного маркером (т.е. начиная с четвертого) факта.
Этот (четвертый) факт согласуется с подцелью, F получает значение cyl, H - 3, и это значение H удовлетворяет двум оставшимся в запросе предикатам. Таким образом будет найден первый ответ на запрос. Ввод пользователем символа ";" инициирует механизм бэктрекинга и, следовательно, дальнейший поиск с использованием рассмотренной схемы.

Правила

Описанные выше возможности языка ПРОЛОГ для работы с базой фактов во многом совпадают с возможностями, предоставляемыми системами управления реляционными базами данных. Языком представления знаний ПРОЛОГ делают имеющиеся в нем средства для описания базы правил. Правило представляет собой дизъюнкт Хорна, содержащий один положительный литерал и несколько отрицательных, и записывается следующим образом:

<структура-0> :- <структура-1>, ..., <структура-N>.
Здесь каждая структура представляет собой предикат, областью действия переменных является все правило. Предикат, стоящий слева от атома ":-" , называется заголовком правила, все остальные предикаты образуют его тело. Правило может трактоваться следующим образом: предикат, являющийся заголовком правила, истинен (удовлетворен) тогда, когда истинен каждый из предикатов тела правила.

Пример. Правила, задающие различные допустимые конструкции башен в рассматриваемой нами предметной области, записываются на языке ПРОЛОГ так, как это показано ниже.


tower(Fr, Fc, Nbl, H) :- roof(Fr, Hr), col(Fc, Nbl, Hc), H is Hr + Hc.	 /* 7 */
col(F, 1, H) :- block(F, H).						 /* 8 */
col(F, 2, H) :- block(F, H1), block(F, H2), H is H1+H2.		 	 /* 9 */
col(F, 3, H) :- block(F, H1), block(F, H2), block(F, H3), H is H1+H2+H3. /* 10 */
Здесь переменные в утверждении 7 имеют такой смысл: Fr -форма крыши башни, Fc - форма блоков ствола башни, Nbl - количество блоков в башне, H - высота башни, Hr - высота крыши, Hc - высота ствола. В утверждениях 8 ... 10 переменная F означает форму блоков ствола, H - высоту ствола, H1, H2, H3 - высоту блоков. Напомним, что область действия переменной ограничена только одним утверждением, поэтому, например, переменная H в правилах 7 и 8 означают разные объекты.

Часто при интерпретации правил атом ":-" удобно читать как "это есть". Тогда правило 7 может быть прочитано следующим образом: башня высотой Н , состоящая из Nbl блоков формы Fc и имеющая крышу формой Fr - это есть крыша формой Fr и высотой Hr, ствол высотой Hc, состоящий из Nbl блоков формы Fc, и высота башни H равна сумме высот крыши Hr и ствола Hc.

Наличие правил в программах на языке ПРОЛОГ позволяет интерпретатору находить ответ на запросы, не касающиеся непосредственно содержимого базы фактов. В вашей предметной области таким запросом может быть, например, запрос на конструирование башни высотой 11 из блоков цилиндрической формы:


?-tower(F_roof, cyl, N_blocks, 11).
Первым ответом будет F_roof = plane, N_blocks = 2. Рассмотрим процесс получения ответа. Интерпретатор, получив запрос, осуществляет поиск предиката tower среди фактов и заголовков правил. В нашем случае будет найдено правило (утверждение 7), в результате сопоставления заголовка правила и запроса будут конкретизированы переменные правила Fc и H (значениями cyl и 11, соответственно) и связаны переменные F_roof и Fr, а также N_blocks и Nbl.
В результате возникают 3 новые подцели, которые необходимо удовлетворить. Интерпретатор начинает с крайней левой - roof(Fr, Hr). Эта подцель согласуется с первым утверждением базы, переменные Fr и связанная с ней F_roof конкретизируются значением prizm, а переменная Hr - значением 4.
Далее интерпретатор возвращается к предикату col(cyl, Nbl, Hc) правила 7 и, рассматривая его как подцель, пробует согласовать с базой правил и фактов. Первым подходящим для согласования утверждением является восьмое - правило, описывающее стволы из одного блока. Этим блоком является цилиндр высотой 5 (утверждение 3). В результате конкретизации переменная Nbl принимает значение 1, а переменная Hc - 5.
Предикат проверки равенства (третья подцель в правиле 7) является встроенным, его истинность интерпретатор проверяет, не обращаясь к базе утверждений. Поскольку H имеет значение 11, Hr - 4, а Hc - 5, то эта подцель неудовлетворена и происходит возврат к соседней слева подцели (col) с тем, чтобы попытаться согласовать ее заново. Эта попытка приводит к тому, что в качестве ствола выбирается ствол, состоящий из одного блока цилиндрической формы, но высотой 3 (а не 5, как ранее). Ясно, что значением предиката равенства по-прежнему будет ложь и вновь потребуется пересогласование подцелей.
Таким образом, интерпретатор осуществит полный перебор вариантов построения башни с призматической крышей и вынужден будет использовать в конструкции башни плоскую крышу. Этот выбор в конечном итоге и обеспечит получение ответа на запрос.

Рассмотренные примеры позволяют сделать ряд важных заключений, касающихся языка ПРОЛОГ. Во-первых, язык ПРОЛОГ позволяет создавать программы, обладающие свойством недетерминизма, т.е. программы, способные давать ответы на запросы, в которых априорно не фиксируются объекты, являющиеся исходными и результирующими. Так, для нашей программы с башнями вполне корректен будет запрос на проектирование башни с призматической крышей из трех блоков:


?-tower(prizm, F, 3 ,H).
Во-вторых, утверждения программ на языке ПРОЛОГ в значительной степени независимы друг от друга, что позволяет легко модифицировать программы, пополняя и изменяя факты и правила.
В-третьих, интерпретатор системы программирования ПРОЛОГ, реализуя линейную по входу версию метода резолюции, осуществляет поиск ответа на запрос полным перебором, используя стратегию поиска в глубину. Для реализации режима с возвращением применяется механизм бэктрекинга. С целью сокращения количества вариантов перебора в больших программах на языке ПРОЛОГ целесообразно включать в правила условия эвристического характера.
Пример. Эффективность исполнения нашей программы при выполнении запросов, связанных с конструированием башни требуемой высоты, значительно повысится, если правила будут переписаны следующим образом.

tower(Fr,Fc,Nbl,H):-roof(Fr,Hr),Hc is H-Hr,col(Fc,Nbl,Hc).
col(F,1,H):-H>1,H<6,block(F,H).
col(F,2,H):-H>3,H<11,block(F,H1),H2 is H-H1,block(F,H2).
col(F,3,H):-H>5,H<16,block(F,H1),block(F,H2),H3 is H-H1-H2,block(F,H3).
Введение в правила 7 ... 10 предикатов меньше и больше сузило их "область применения" и тем самым уменьшило количество вариантов перебора при поиске ответа, но лишило программу двух важных свойств - недетерменизма и независимости утверждений в базах. Так, теперь стали невыполнимы запросы на проектирование башен произвольной высоты.

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


col(F, 1, H) :- block(F, H).
col(F, Nbl, H) :- block(F, Hbl), col(F, N, Hc), H is Hbl+Hc, Nbl is N+1.
Это определение интерпретируется следующим образом: 1) ствол - это есть один блок, при этом форма и высота ствола совпадают с формой и высотой блока, или 2) ствол - это есть блок и "подствод", при этом высота ствола равна сумме высот блока и "подствола", а количество блоков в стволе на единицу больше, чем в "подстволе". Пара этих утверждений способна породить ствол из любого количества блоков.

Необходимо отметать, что использование рекурсивных определений такого типа в программах на языке ПРОЛОГ связано с опасностью бесконечного "зацикливания" в рекурсиях. Такой, например, будет ситуация в нашем примере при запросе на конструирование башни высотой 11, когда интерпретатор будет исследовать бесконечное число проектов башен с призматической крышей и цилиндрическим стволом высотой кратной пяти. В этом проявляется упомянутое ранее свойство полуразрешимости ИППП, лежащего в основе языка ПРОЛОГ.

Отмеченные достоинства языка ПРОЛОГ - декларативная форма представления знаний о предметной области, недетерминизм, легкая модифицируемость программ - обусловили его широкое применение в различных проектах ИИ. Системы программирования ПРОЛОГ имеются для всех универсальных ЭВМ.

Списки и рекурсия в ПРОЛОГЕ

Список - упорядоченная последовательность элементов, имеющая произвольную длину. В ПРОЛОГЕ элементы списка не обязаны принадлежать одному типу и могут быть в свою очередь списками.
Список в ПРОЛОГе - это либо пустой список, обозначаемый [], либо структура с функтором "."(точка) и двумя компонентами: головой и хвостом списка. Признаком конца списка служит хвост, являющийся пустым списком [].
Например:
[]- пустой список
.(a,[])- список из одного элемента a
.(a,.(b,.(c,[]))) - список из трёх элементов a, b, c
В некоторых реализациях ПРОЛОГа функтор "." определён как инфиксный оператор, тогда третий пример будет иметь вид a.(b.(c.[])).
Однако в любом случае использование "." оказывается неудобным, поэтому в ПРОЛОГе предусмотрена привычная форма записи списков: [], [a], [a,b,c].

Списки, как и любые другие структуры, могут содержать переменные, например, допустим такой список [X,a,[b,X,Y],Z].

Работа со списками основана на расщеплении их на голову и хвост. Голова - это первый элемент списка, хвост - список из остальных элементов.
Пример.
СписокГоловаХвост
[a, b, c]a[b, c]
[[a, b], c][a, b][c]
[a]a[]
[]НетНет
Пустой список [] не имеет ни головы, ни хвоста.
В ПРОЛОГе используется специальная форма представления списка с головой H и хвостом T - [H|T].

Примеры сопоставления списков:


?- [X,Y,Z]=[a,b,c].		X = a, Y = b, Z = c
?- [cat]=[X|Y].			X = cat, Y = []
?- [[a,Y]|Z]=[[X,b],[c,d]].	X = a, Y = b, Z = [[c,d]]

Примечание. Существует ещё одна область применения списков - представление строк литер (символов): строка, заключённая в двойные кавычки (") эквивалентна списку десятичных значений ASCII.

Для работы со списками в ПРОЛОГе используется рекурсия. Рассмотрим пример, в котором необходимо определить правила проверки принадлежности элемента списку. Для этого дадим рекурсивное определение принадлежности - некоторый элемент принадлежит списку, если а) он является первым элементом списка (головой списка) или б) принадлежит хвосту списка.


member(X, [Y|Z]):- X=Y.		или
member(X, [Y|_]):- X=Y.		или
member(X, [X|_]).
member(X, [_|Y]):- member(X,Y).
Для правильного применения рекурсии (а это очень мощное средство) необходимо обращать внимание на два момента:

Рассмотрим, как работают наши правила на следующих запросах: member(a,[a,b,c,d,e]) и member(d,[a,b,c,d,e]).

При получении запроса в виде


?-member(a,[a,b,c,d,e]).
ПРОЛОГ устанавливает маркер на первое правило для member и начинает попарное сопоставление компонентов предиката-запроса и заголовка этого правила. При сопоставлении первых компонентов происходит конкретизация переменной X из заголовка значением a из запроса, при сопоставлении вторых - удачное сравнение двух атомов a (одного - из запроса, другого - из заголовка).

При получении запроса в виде


?-member(d,[a,b,c,d,e]).
ПРОЛОГ закончит неудачей попытку использовать первое правило для member и установит маркер первого уровня на второе правило. Использование этого правила порождает подцель в виде member(d,[b,c,d,e]). Для реализации этой подцели ПРОЛОГ вынужден опять использовать второе правило, установив на него маркер второго уровня, при этом возникает очередная подцель member(d,[c,d,e]). На третьем уровне рекурсии ПРОЛОГ вновь использует второе правило, что инициирует подцель member(d,[d,e]), которая удовлетворяется на четвертом уровне рекурсии благодаря первому правилу для member.

Задание. Самостоятельно рассмотреть процесс поиска ответов ПРОЛОГом на следующие запросы:


?-member(a,[]).
?-member(a,X).
?-member(a,[b,C,d,e]).

Отсечение

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

Синтаксически отсечение представляется функтором "!" структуры без аргументов. Встроенный предикат "!" всегда согласуется с базой правил и не может быть пересогласован. Однако он имеет существенный побочный эффект, изменяющий порядок последующего возврата.
Рассмотрим следующий пример:


C:-F,G,H,!,P,Q,R.  /* 1 */
C:-S,T,U.          /* 2 */
?-A,B,C,D,E.
Выполняя запрос, ПРОЛОГ без каких-либо ограничений может выполнять возврат среди подцелей {A,B и F,G,H} или {A,B и S,T,U}, но до тех пор, пока не пройдет "однонаправленную калитку" "!". Здесь он опять может беспрепятственно возвращаться среди {P,Q,R}. Но если ПРОЛОГу потребуется выполнить возврат левее "!", то будет совершен прыжок сразу к цели B.
То есть, если отсечение "!" встречается в качестве подцели в некотором правиле (или запросе), то ПРОЛОГу запрещается пересогласовывать все ранее согласованные подцели (лежащие левее знака "!") и цель, инициировавшую использование данного правила.
Так, в примере, если ПРОЛОГ перешагнет знак "!" в правиле 1, то правило 2 уже рассмотрено быть не может, пока ПРОЛОГ не пересогласует цель B.

Можно выделить 3 основных случая использования отсечения.

  1. Указание интерпретатору ПРОЛОГа, что найдено необходимое правило для заданной цели.
  2. Указание интерпретатору ПРОЛОГа, что необходимо немедленно прекратить доказательство конкретной цели, не пытаясь рассматривать какие-либо альтернативы (конструкция "!,fail").
  3. Указание интерпретатору ПРОЛОГа, что в ходе перебора альтернативных вариантов найдено необходимое решение, и нет смысла вести перебор далее.

Пример. Пусть необходимо написать правила для вычисления суммы ряда натуральных чисел 1, 2, ... N. Напрашивается пара следующих утверждений:


sum(1,1).                                    /* 1 */
sum(N,S):-N1 is N-1, sum(N1,S1), S is S1+N.  /* 2 */
Выполним следующий запрос:

?-sum(5,X).      X=15
;                в результате - бесконечная рекурсия!
Для исправления ситуации необходимо модифицировать первое правило следующим образом (это пример первого случая использования отсечения):

sum(1,1):-!.                                 /* 1' */
Теперь попробуем такой запрос:

?-sum(-5,X).     в результате - опять бесконечная рекурсия!
Поэтому перед первыми двумя добавляем еще одно утверждение (это пример второго случая использования отсечения):

sum(N,_):-N=<0, !, fail.                  /* 0 */

Примером третьего случая использования отсечения может служить ПРОЛОГ-программа для игры в "крестики-нолики". В этой игре (как и во многих других) существует понятие "вынужденный ход", не подразумевающий альтернативы. Понятно, что код, отвечающий за анализ игровой позиции должен содержать отсечение в правилах, диагностирующих "вынужденный ход".

Следует отметить, что использование отсечения требует хорошего знания стратегии работы ПРОЛОГ-системы!

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

Классификация термов
var(X) - согласуется, если X - неконкретизированная переменная.
nonvar(X) - согласуется, если X - конкретизированная переменная.
atom(X) - согласуется, если X - атом.
integer(X) - согласуется, если X - число.
atomic(X) - согласуется, если X - атом или число.

Работа с утверждениями
asserta(X), assertz(X) - добавляют утверждение в ПРОЛОГ-программу, при этом значение X должно быть таким, чтобы могло интерпретироваться как утверждение.
retract(X) - удаляет из программы первое утверждение, соспоставимое (унифицируемое) с X.
Примечание. Рассмотренные предикаты необратимы, т.е. их действие не отменяется при возврате.
clause(X,Y) - X и Y сопоставляются с заголовком и телом имеющегося в программе утверждения К этому моменту X должен быть достаточно конкретизирован. При возврате - переход на следующее утверждение. Для фактов Y конкретизируется значением true.
Примечание. Напоминаем, что любое утверждение ПРОЛОГа - структура, где ":-", "?-", "," и т.п. - функторы, имеющие вид инфиксных операторов.

Работа со стуктурами
functor(S,F,N) - согласуется при условии, что S - структура с функтором F и количеством аргументов N. Используется а) для анализа существующих структур и б) для создания структур с заданными функтором и количеством аргументов.
arg(N,S,X) - сопоставляет N-ый аргумент структуры S с X.
S=..L - сопоставляет структуру S со списком L, первый элемент которого рассматривается как функтор, а остальные - как аргументы. Также используется двояко.
name(A,L) - сопоставляет атом A и список десятичных ASCII-кодов L.

Составные цели
"," - коньюнкция целевых утверждений.
";" - дизъюнкция целевых утверждений.
call(X) - согласуется, если согласуется X, рассматриваемое как целевое утверждение.
not(X) - согласуется, если не согласуется X, рассматриваемое как целевое утверждение.
bagof(T,G,L) - порождает список L всех объектов T, удовлетворяющих цели G, принимая во внимание одинаковость конкретизации остальных переменных цели G.
setof(T,G,L) - аналогичен bagof(), но дополнительно упорядочивает список L, исключая дублирующие элементы.
findall(T,G,L) - порождает список L всех объектов T, удовлетворяющих цели G, игнорируя различия в конкретизации остальных переменных цели G.

Ввод-вывод
write(X) - записывает терм X в текущий поток вывода.
nl - выводит символ "новая строка".
tab(N) - выводит N пробелов.
put(X) - выводит символ, имеющий десятичный ASCII-код X.
read(X) - читает очередной терм из входного потока, сопоставляя его с X (при этом читаемый терм должен заканчиваться точкой).
get0(X) - сопоставляет X и следующий символ из входного потока.
get(X) - то же, что и get0(X), но только для символов, имеющих графическое изображение.
skip(X) - пропускает символы входного потока, пока не встретится сопоставимый с X.
Примечание. Все предикаты ввода-вывода необратимы, т.е. их действие при возврате не отменяется, пересогласовать их невозможно.