Так как clygate не сопоставимо с curragh_tip, то происходит сопоставление со вторым правилом для принадлежит. Переменная Y получает значение [music_star, park_mill, portland], и порождается следующая цель: определить, принадлежит ли clygate этому списку. Опять происходит сопоставление со вторым правилом, и снова выделяется хвост списка. Текущей целью становится принадлежит (clugate,[park_mill,portland]) Этот процесс рекурсивно повторяется до тех пор, пока мы не доберемся до цели, у которой X есть clygate, a Y есть [portland]. Происходит еще одно сопоставление со вторым правилом, и теперь Y конкретизируется хвостом списка [portland], который является пустым списком. Следующей целью становится принадлежит(clygate,[]). Ни одно из правил в базе данных не сопоставимо с этой целью, так что цель оказывается ложной и ответ на вопрос будет отрицательным.
Важно помнить, что каждый раз, когда при согласовании принадлежит с базой данных выбирается второе утверждение этого предиката, Пролог рассматривает рекурсивное обращение к предикату принадлежит как попытку найти соответствие для некоторой новой его «копии». Это предотвращает путаницу переменных, соответствующих одному употреблению утверждения, с переменными, соответствующими другому употреблению этого же утверждения.
Предикат отношения принадлежности настолько полезен, что мы еще неоднократно будем использовать его в оставшейся части этой книги. Предикат принадлежит важен еще и потому, что он представляет практически наименьший полезный пример рекурсивного предиката – определение предиката принадлежит содержит утверждения, которые могут быть проверены с помощью только того же самого предиката принадлежит. Рекурсивные определения часто встречаются в программах на Прологе, и они полностью равноправны с другими определениями. Однако надо быть осторожным, чтобы не допускать «закольцованные» определения, как, например, следующее:
родитель(X,Y):- ребенок(Y,X).
ребенок(A,B):- родитель(B,A).
В этом примере, чтобы согласовать с базой данных целевое утверждение родитель, необходимо согласовать подцель ребенок. Однако определение для ребенок приведет к появлению единственной подцели – родитель. Вы должны понимать, почему вопрос, содержащий в качестве целей родитель или ребенок, приводит к циклу, находясь в котором Пролог не сможет найти какие-либо новые факты, и этот цикл никогда не завершится.
Одна важная проблема, на которую следует обращать внимание в рекурсивных определениях, - это
человек(X):- человек(Y), мать(Х,Y).
человек(адам).
и ввели вопрос
?- человек(X).
то Пролог сначала использовал бы правило и рекурсивно породил подцель человек (Y). Попытка найти соответствие этой цели вновь привела бы к выбору первого правила и породила бы еще одну новую эквивалентную подцель. И так далее, до тех пор, пока не исчерпались бы вычислительные ресурсы. Конечно, если бы была возможность использовать механизм возврата, то был бы найден сообщенный в определении факт об Адаме и началось бы порождение решений[7]. Ошибка заключается в том, что для того, чтобы начался возврат, Пролог должен потерпеть неудачу при проверке первого утверждения. В данном же случае поиск решения оказывается неопределенно длинным, и нет никакой возможности завершить этот поиск с успехом либо с неудачей. Из всего сказанного выше можно извлечь следующую мораль:
Не следует предполагать, что только потому, что вы предоставили все относящиеся к делу факты и правила, Пролог всегда найдет их. Создавая программу на Прологе, вы все время должны представлять, каким образом Пролог осуществляет поиск в базе данных и какие переменные будут конкретизированы, когда будет использовано одно из ваших правил.
Для приведенного примера имеется простой способ устранения ошибки – поместить факт перед правилом, а не после него. В действительности существует хороший эвристический принцип: помещать, где это возможно, факты перед правилами. Иногда при размещении правил в некотором конкретном порядке может возникнуть ситуация, когда они будут правильно работать для целей одного вида и не будут работать для целей другого вида. Рассмотрим следующее определение предиката список (X), при котором предикат список является истинным, если X – список, последний элемент которого имеет в качестве хвоста пустой список:
список([A|B]):- список(B).
список([]).
Если мы используем эти правила для получения ответов на вопросы, подобные следующим:
?- список ([a,b,c,d]).
?- список([]).
?- список(f(1,2,3))
то данное определение будет работать хорошо. Но если мы сделаем запрос
?- список(X).