Текст книги "Программирование на языке пролог"
Автор книги: У. Клоксин
Соавторы: К. Меллиш
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 5 (всего у книги 26 страниц)
Когда целевое утверждение недоказуемо (проверены все возможные утверждения или пользователь нажал клавишу ';'), «цепочка доказательств» проходит назад тот путь, по которому она пришла в данную точку. Она возвращается в покинутые перед этим прямоугольники для того, чтобы попытаться передоказать(вновь согласовать) соответствующие целевые утверждения. Когда стрелка возвращается в то место, где было выбрано какое-то утверждение (это событие изображается числом в скобках), Пролог пытается найти альтернативное утверждение, соответствующее данной цели. Сначала делаются неопределенными все переменные, которые были конкретизированы в ходе доказательства данного целевого утверждения. Затем возобновляется поиск в базе данных, начиная с того места, где был оставлен маркер. Если будет найдено другое утверждение, соответствующее целевому, Пролог помечает это место, и дальше события развиваются, как было описано выше в разд. 2.6.1. Отметим, что рассмотрение любых целевых утверждений, находящихся «ниже» данного (даже если они были пройдены в ходе рассмотрения предыдущей альтернативы), всегда начинается с самого начала. Пролог пытается доказать их без учета положения маркера (т. е. это не передоказательство). Если не удается найти другое подходящее утверждение, данное целевое утверждение считается недоказуемым и стрелка продолжает возвращаться назад до следующего маркера. В нашем примере, если целевое утверждение родители(джон,анна,фред)недоказуемо, стрелка уйдет назад из прямоугольника родители(джон,анна,фред)и войдет в прямоугольник родители (мэри,анна,фред)снизу для того, чтобы попытаться передоказать данное целевое утверждение (см. рис. 2.5).
Рис. 2.5.
Отступая дальше, стрелка достигнет места, где было выбрано утверждение, соответствующее целевому утверждению отец. В первую очередь освобождаются все переменные, которые были конкретизированы в результате использования данного утверждения. Это означает, что переменная Fвновь становится неконкретизированной. Затем Пролог просматривает базу данных, начиная с утверждения, следующего за первым утверждением с предикатом отец(здесь находится маркер), пытаясь найти альтернативное утверждение. Если предположить, что мэриимеет только одного отца, то этот процесс успехом не завершится. Поэтому стрелка продолжит отступление. Она покинет прямоугольник отец(мэри, F)(это целевое утверждение недоказуемо) и вернется в прямоугольник мать(мэри,анна)(для того, чтобы попытаться передоказать данное целевое утверждение) (см. рис. 2.6).
Рис. 2.6.
Отступление стрелки будет продолжаться до успешного доказательства соответствующего целевого утверждения.
Эти примеры иллюстрируют общую схему повторного рассмотрения целевых утверждений в процессе возврата. Когда некоторое целевое утверждение недоказуемо, стрелка возвращается из соответствующего прямоугольника в прямоугольник с предшествующим целевым утверждением. Стрелка отступает до тех пор, пока не встретится маркер. Все переменные, которые были конкретизированы в результате предыдущего выбора сопоставимого утверждения, становятся неконкретизированными. Затем Пролог возобновляет поиск в базе данных сопоставимого утверждения, начиная с маркера. Если сопоставимое утверждение будет найдено, новое место помечается маркером, создаются прямоугольники для целевых подутверждений и стрелка опять начинает движение вниз. В противном случае стрелка продолжает отступать вверх в поисках другого маркера.
2.6.3. Установление соответствияПравила, определяющие, подходит ли некоторое утверждение для согласования с целевым утверждением, выглядят следующим образом. Отметим, что при выборе утверждения все переменные сначала неконкретизированы.
• Неконкретизированная переменная соответствует любому объекту. Этот объект становится значением переменной.
• Целое число или атом соответствуют только самим себе.
• Между структурами можно установить соответствие, только если они имеют одинаковый функтор, одинаковое число параметров и соответствующие параметры соответствуют друг другу.
Особым случаем является установление соответствия между двумя неконкретизированными переменными. В этом случае мы говорим, что переменные сцеплены.Две сцепленные переменные обладают следующим свойством: как только одна из них принимает конкретное значение, то же самое конкретное значение принимает и другая.
Если читатель заметил сходство между установлением соответствия и приравниванием аргументов (разд. 2.4), то он совершенно прав. Дело в том, что предикат '=' пытается сделать свои аргументы равными путем установления соответствия между ними.
Попытаемся применить на практике наши знания об операторах, арифметических действиях и установлении соответствия. Предположим, что в базе данных находятся следующие факты:
сумма(5).
сумма(З).
сумма(X+Y).
Рассмотрим вопрос:
?– сумма(2+3).
Какой из вышеприведенных фактов будет соответствовать данному запросу? Если вы думаете, что таковым будет первый факт, вам следует вернуться назад и еще раз прочесть разделы о структурах и операторах. В вопросе аргументом структуры суммаявляется структурас функтором + и компонентами 2 и 3. На самом деле указанной цели соответствует третий факт, при этом переменные Xи Yпринимают конкретные значения 2 и 3.
С другой стороны, если программист действительно хочет вычислить сумму, ему следовало бы воспользоваться предикатом is. Он должен был бы написать
?– X is 2+3.
или (в качестве развлечения) он мог бы определить предикат сложить, связывающий два целых числа и их сумму:
сложить (X, Y, Z):– Z is X+Y.
В этом определении Xи Yдолжны быть конкретизированы, а Zнеконкретизирована.
ГЛАВА 3. ИСПОЛЬЗОВАНИЕ СТРУКТУР ДАННЫХ
Оксфордский толковый словарь английского языка определяет слово «рекурсия» следующим образом:
РЕКУРСИЯ. [Теперь употребляется редко, устаревшее.] Обратное движение, возвращение.
Это определение загадочно и, по-видимому, устаревшее. В настоящее время рекурсия является очень популярным и мощным средством в области нечислового программирования. Она используется в двух случаях: для описания структур, имеющих другие структуры в качестве компонент, и для описания программ, выполнению которых предшествует выполнение их собственной копии. Иногда начинающие программисты относятся к рекурсии с подозрением, не понимая, как это можно определить некоторое отношение через само себя? В Прологе рекурсия – это нормальный и естественный способ представления структур данных и программ. Мы надеемся, что тема этой главы – рекурсия – обретает ясность удобным и ненавязчивым образом.
3.1. Структуры и деревья
Чтобы легче было понять сложную структуру, ее обычно представляют в виде дерева,в котором каждому функтору соответствует вершина, а компонентам соответствуют ветви дерева. Каждая ветвь может указывать на другую структуру, так что мы можем иметь структуры внутри структур. Обычно принято изображать дерево таким образом, чтобы корень дерева находился вверху, а ветви были направлены вниз, как это показано на рис. 3.1. Заметим, что два последних дерева имеют одинаковую форму, хотя корни и листья деревьев различны. Прежде чем читать дальше, вы должны быть уверены в том, что можете представить в виде дерева каждую из структур, с которыми вы уже сталкивались в предыдущих главах.
Предположим, у нас есть предложение «Джону нравится Мэри», и необходимо представить синтаксическую структуру этого предложения. В английском языке имеется очень простое синтаксическое правило построения предложений: предложение состоит из существительного, за которым следует глагольная группа. В свою очередь глагольная группа состоит из глагола и другого существительного. Это отношение между частями предложения может быть описано следующей структурой (которая представлена в виде дерева, приведенного на рис. 3.2): предложение(существительное, глагольная_группа(глагол, существительное)).
Рис. 3.1.
Если мы возьмем наше предложение («Джону нравится Мэри») и вставим слова из этого предложения в качестве аргументов функторов существительноеи глаголв структуру предложения, то мы получим (см. рис. 3.3):
предложение(существительное(джон), глагольная_группа(глагол(нравится), существительное(мэри)))
Этот пример показывает, как можно использовать структуры в языке Пролог для представления синтаксиса очень простых предложений. В общем случае если мы знаем, какой частью речи является каждое слово в предложении, то можно записать структуру на Прологе, которая в явном виде описывает отношения между различными словами в предложении. Эта задача сама по себе представляет интересную тему исследования, и далее мы еще вернемся к вопросу о том, как, используя Пролог, заставить ЭВМ «понимать» некоторые простые предложения.
Деревья могут также применяться для графического описания переменных внутри структуры, в частности показывая, как сцеплены переменные, имеющие одинаковые имена (см. рис. 3.4).
Рис. 3.2.
Рис. 3.3.
Рис. 3.4.
3.2. Списки
Список– довольно широко используемая структура данных в области числового программирования. Список -это упорядоченная последовательность элементов,которая может иметь произвольную длину. Признак упорядоченныйуказывает на то, что порядок элементов в последовательности является существенным. Элементамисписка могут быть любые термы – константы, переменные, структуры, которые включают, конечно, и другие списки. Указанные свойства очень полезны в ситуации, когда мы не в состоянии заранее предсказать, насколько большим должен быть список и какую информацию он будет содержать. Более того, списки позволяют представить практически любой тип структуры, который может потребоваться при символьных вычислениях. Списки широко используются для представления деревьев синтаксического разбора, грамматик, карт городов, программ для ЭВМ и математических объектов, таких как графы, формулы и функции. Существует язык программирования – Лисп, в котором единственными доступными структурами данных являются константа и список. Однако в Прологе список – это просто один из частных видов структуры.
Списки могут быть представлены как специального вида дерево. Список – это любой пустой список,не содержащий ни одного элемента, либо структура, имеющая две компоненты: голову и хвост списка. Конец списка обычно представляют как хвост, который является пустым списком. Пустой список записывают как [] – открывающая квадратная скобка, за которой следует закрывающая квадратная скобка. Голова и хвост списка являются компонентами функтора, обозначаемого точкой '.'. Так, список, состоящий из одного элемента ' а' есть .(а, []), а его представление в виде дерева имеет вид
Аналогично список, состоящий из атомов a, bи с, мог бы быть записан как .(а,.(b,.(с,[]))), что изображается следующим образом:
Иногда функтор точка ('.') определяется как оператор, так что допустимо для Пролога два последних списка записать как а.[]и а.(b.(с.[]))). Второй список можно было бы записать просто как а.b.с.[], так как функтор точка – правоассоциативный оператор. Списки являются упорядоченными последовательностями элементов, так что список а.bотличается от списка b.а.
Некоторые любят записывать древовидные диаграммы списков в виде дерева, «растущего» слева направо, ветви которого направлены вниз. Приведенный выше список, представленный диаграммой в виде такой «виноградной лозы» выглядит так:
В этой диаграмме компонента функтора '.', соответствующая голове списка, «свисает» вниз, а компонента, соответствующая хвосту списка, «растет» вправо. Конец списка четко выделен тем, что последняя компонента – хвост списка – является пустым списком. Главное преимущество использования диаграммы для представления списка заключается в том, что она может быть записана справа налево на листе бумаги.
«Виноградная» диаграмма может оказаться удобной для записи списков на бумаге, когда нам необходимо видеть структуру списка, но в программах на Прологе для записи списков такие диаграммы не используются. Так как запись сложных списков с помощью функтора '.' часто оказывается неудобной, то в Прологе предусмотрена другая синтаксическая форма, которая может быть использована для записи списков в программе. Это так называемая скобочная форма записи списка.Она представляет собой заключенную в квадратные скобки последовательность элементов списка, разделенных запятыми. Например, упоминавшиеся выше списки могут быть записаны в скобочной форме в виде [а]и [а, b, с]. Списки могут содержать другие списки или переменные. Например, в Прологе допустимы следующие списки:
[]
[конкретный, человек, [любит, ловить, рыбу]]
[а, V1, b, [X, Y]]
Переменные, входящие в списки, ничем не отличаются от переменных в любой другой структуре. Они в любой момент могут быть конкретизированы, так что умелое использование переменных может обеспечить образование «пустых мест» в списке, которые впоследствии могут быть заполнены данными.
Чтобы продемонстрировать структуру списков, содержащих в качестве элементов другие списки, приведем «виноградную» диаграмму для последнего из рассмотренных списков:
Из приведенной диаграммы ясно видно, что каждый ее горизонтальный уровень является списком, состоящим из некоторого числа элементов. Верхний уровень на приведенной диаграмме представляет список, содержащий четыре элемента, один из которых сам является списком. Второй уровень, содержащий два элемента, представляет четвертый элемент списка верхнего уровня. Работа со списками основана на расщеплении их наголовуи хвостсписка. Голова списка – это первый аргумент функтора '.', который используется для конструирования списка. Хвост списка – это второй аргумент функтора '.'. В случае когда для записи списка используется скобочная форма записи, головой списка является первый его элемент. Хвост списка представляет список,состоящий из всех элементов исходного списка, за исключением первого его элемента. Следующие примеры демонстрируют расщепление списков на голову и хвост:
Список | Голова | Хвост |
[а, b, с] | а | [b, с] |
[а,] | а | [] |
[] | ||
[[эта, кошка], сидела] | [эта, кошка] | [сидела] |
[эта, [кошка, сидела]] | эта | [кошка, сидела]] |
[эта, [кошка, села], на пол] | эта | [кошка, села], на пол] |
[X+Y, х + y] | X + Y | [x + y] |
Заметим, что пустой список не имеет ни головы, ни хвоста. В последнем примере оператор ± используется как функтор для структур +(Х, Y)и +(х,у).
Так как операция расщепления списка на голову и хвост очень широко используется, то в Прологе введена специальная форма для представления списка с головой Xи хвостом Y. Это записывается как [X|Y], где для разделения Xи Yиспользуется вертикальная черта. При конкретизации структуры подобного вида Xсопоставляется с головой списка, a Y– с хвостом списка, как это показано в следующем примере:
p([1, 2, 3]).
p([эта, кошка, сидела, [на, этой, подстилке]]).
?– p([X|Y]).
X = 1 Y=[2,3] ;
X=эта Y=[кошка, сидела, [на, этой, подстилке]]
?– p([_,_,_,[_|X]]).
X=[этой, подстилке]
Ниже приведено еще несколько примеров с использованием различных синтаксических возможностей записи списков, показывающих, каким образом производится сопоставление списков. В этих примерах делается попытка сопоставить два заданных списка, конкретизируя переменные, если это возможно.
Список 1 | Список 2 | Конкретизация |
[X, Y, Z] | [джону,нравится,рыба] | X=джону Y= нравится Z = рыба |
[кошка] | [X| Y] | X= кошка |
[X, Y | Z] | [мэри,нравится,вино] | X = мэри Y = нравится Z = [вино] |
[[этот, Y]|Z] | [[X, заяц], [находится, здесь]] | X = этот Y = заяц Z = [[находится_ здесь]] |
[X, Y|Z, W] | (синтаксически некорректная конструкция списка) | |
[золотистый | T] | [золотистый, норфолк] | T= [норфолк] |
[лошадь, X] | [белая, лошадь] | (сопоставление невозможно) |
[белая | Q] | [P | лошадь] | P= белая Q= лошадь |
Как видно из последнего примера, используя скобочную форму записи списков, можно создавать структуры, похожие на списки, но не заканчивающиеся пустым списком. Одна из таких структур, [белая|лошадь],обозначает структуру, головой которой является белая,а хвостом – лошадь.Константа лошадьне является ни списком, ни пустым списком, и, как мы увидим далее, обработка таких структур требует большой осторожности, когда они используются в качестве хвоста списка.
Существует еще одна область применения списков – это представление строк литер. Иногда возникает необходимость в использовании строк литер для печати или ввода текста. Если строка литер заключена в двойные кавычки, то эта строка представляется как список кодов, соответствующих литерам строки. Для кодировки литер используется код ASCII, который обсуждался в разд. 2.2. Например, строка «system»преобразуется в Прологе в следующий список: [115, 121, 115, 116, 101, 109].
3.3. Принадлежность элементов списку
Предположим, что имеется некоторый список, в котором Xобозначает его голову, a Y– хвост списка. Напомним, что такой список мы можем записать так: [X|Y]. Этот список мог бы содержать, например, клички тех лошадей потомков жеребца Coriander,которые все выиграли скачки в Великобритании в 1927 году:
[curragh_tip, music_star, park_mill, portland]
Теперь предположим, что мы хотим определить, содержится ли некоторая кличка в указанном списке. В Прологе это можно сделать, определив, совпадает ли данная кличка с головой списка.
Если совпадает, то наш список завершается успехом. Если нет, то мы проверяем, есть ли кличка в хвосте исходного списка. Это значит, что снова проверяется голова, но уже хвостасписка. Затем проверяется голова очередногохвоста списка. Если мы доходим до конца списка, который будет пустым списком, то наш поиск завершается неудачей: указанной клички в исходном списке нет.
Для того чтобы записать все это на Прологе, сначала надо установить, что между объектом и списком, в который этот объект может входить, существует отношение. Это отношение, называемое отношением принадлежности, представляет достаточно распространенное в повседневной жизни понятие. Так, мы говорим о людях, являющихся членами клубов, и о других тому подобных вещах. Для записи этого отношения мы будем использовать предикат принадлежит:целевое утверждение принадлежит(X, Y)является истинным («выполняется»), если терм, связанный с X, является элементом списка, связанного с Y. Имеются два условия, которые надо проверить для определения истинности предиката. Первое условие говорит, что Xбудет элементом списка Y, если X совпадает с головой списка Y. На Прологе этот факт записывается следующим образом:
принадлежит(X,[X |_]).
Эта запись констатирует, что X является элементом списка, который имеет X в качестве головы.Заметим, что мы использовали анонимную переменную '_' для обозначения хвоста списка. Это сделано потому, что мы никак не используем хвост списка в этом частном факте. Заметим, что данное правило могло бы быть записано и по-другому:
принадлежит(X,[Y|_]:– X = Y.
К этому моменту вы должны уже понимать, почему можно использовать Xсразу в двух местах в первой, более короткой, версии этого правила.
Второе, и последнее, правило говорит о том, что Xпринадлежит списку при условии, что он входит в хвост этого списка, обозначаемый через Y. И нет лучшего пути, чем использовать тот же самый предикат принадлежитдля того, чтобы определить, принадлежит ли Xхвосту списка! В этом и состоит суть рекурсии. На Прологе это выглядит так:
принадлежит(X,[_ |Y]):– принадлежит(X,Y).
и констатирует, что X является элементом списка, еслиX является элементом хвоста этого списка.Заметим, что мы использовали анонимную переменную '_', так как нас не интересует имя переменной, обозначающей голову списка. Два этих правила в совокупности определяют предикат для отношения принадлежности и указывают Прологу, каким образом просматривать список от начала до конца при поиске некоторого элемента в списке. Наиболее важный момент, о котором следует помнить, встретившись с рекурсивно определенным предикатом, заключается в том, что прежде всего надо найти граничные условияи способ использования рекурсии.
Для предиката принадлежитв действительности имеются два типа граничных условий. Либо объект, который мы ищем, содержится в списке, либо он не содержится в нем. Первое граничное условие для предиката принадлежитраспознается первым утверждением, которое приведет к прекращению поиска в списке, если первый аргумент предиката принадлежитсовпадает с головой списка, соответствующего второму аргументу. Второе граничное условие встречается, когда второй аргумент предиката принадлежитявляется пустым списком.
Как мы можем убедиться в том, что граничные условия будут когда-либо удовлетворены? Для этого необходимо обратить внимание на то, как используется рекурсия во втором правиле для предиката принадлежит. Заметим, что каждый раз, когда при поиске соответствия для целевого предиката принадлежитпроисходит рекурсивное обращение к тому же предикату, новая цель формируется для более короткогосписка. Хвост списка всегда является более коротким списком, чем исходный список. Очевидно, что рано или поздно произойдет одно из двух событий: либо произойдет сопоставление с первым правилом для принадлежит, либо в качестве второго аргумента принадлежитбудет задан список длины 0, т. е. пустой список. Как только возникнет одна из этих ситуаций, прекратится рекуррентное порождение целей для предиката принадлежит. Первое граничное условие распознается фактом, который не вызывает порождения новых подцелей. Второе граничное условие не распознается ни одним из утверждений для принадлежит, так что процесс поиска сопоставимого элемента списка для целевого утверждения принадлежит закончится неудачей. Это демонстрирует следующий пример на Прологе:
принадлежит(Х, [X | _]).
принадлежит(Х,[_|Y]):– принадлежит (X,Y).
?– принадлежит(d,[a,b,с,d,e,f,g]).
да
?– принадлежит(2,[3,a,4,f]).
нет
Предположим, мы введем вопрос
?– принадлежит(clugatе,[curraugh_tiр,music_star,раrk_mill, ortland]).
Так как 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]7
Это могло бы привести к успеху при соответствующем определении предиката мать. – Прим. ред.
[Закрыть] . Ошибка заключается в том, что для того, чтобы начался возврат, Пролог должен потерпеть неудачу при проверке первого утверждения. В данном же случае поиск решения оказывается неопределенно длинным, и нет никакой возможности завершить этот поиск с успехом либо с неудачей. Из всего сказанного выше можно извлечь следующую мораль:
Не следует предполагать, что только потому, что вы предоставили все относящиеся к делу факты и правила, Пролог всегда найдет их. Создавая программу на Прологе, вы все время должны представлять, каким образом Пролог осуществляет поиск в базе данных и какие переменные будут конкретизированы, когда будет использовано одно из ваших правил.
Для приведенного примера имеется простой способ устранения ошибки – поместить факт перед правилом, а не после него. В действительности существует хороший эвристический принцип: помещать, где это возможно, факты перед правилами. Иногда при размещении правил в некотором конкретном порядке может возникнуть ситуация, когда они будут правильно работать для целей одного вида и не будут работать для целей другого вида. Рассмотрим следующее определение предиката список (X),при котором предикат списокявляется истинным, если X– список, последний элемент которого имеет в качестве хвоста пустой список:
список([A|B]):– список(B).
список([]).
Если мы используем эти правила для получения ответов на вопросы, подобные следующим:
?– список ([a,b,c,d]).
?– список([]).
?– список(f(1,2,3))
то данное определение будет работать хорошо. Но если мы сделаем запрос
?– список(X).
то программа зациклится. Предикат, аналогичный предикату список,но неподверженный зацикливанию, задается следующими двумя фактами:
обобщенный_список([]).
обобщенный_список([_ |_]).
В этом варианте просто проверяется начало списка, а не тот факт, что последний хвост списка является пустым списком ([]). Последнее определение не является таким же строгим тестом правильности списка, как определение предиката список,но оно не приведет к зацикливанию, если аргументом является переменная.