Запросы: как Prolog отвечает

Урок о том, как задавать Prolog вопросы и читать его ответы: true, false, подстановки переменных и перебор решений.

Запрос — это вопрос к базе знаний: Prolog ищет, можно ли подтвердить утверждение, опираясь на известные факты и правила.

В прошлом уроке мы наполнили базу фактами. Но база, к которой нельзя обратиться, бесполезна. Запрос — это способ спросить: «А правда ли, что Том — родитель Боба?» или «Кто вообще дети Тома?». Prolog отвечает, проверяя, согласуется ли вопрос с тем, что записано в базе.

Будем работать с той же семьёй:

parent(tom, bob).
parent(tom, liz).
parent(bob, ann).
parent(bob, pat).

Знак запроса ?-

В интерактивном режиме (его называют toplevel) Prolog показывает приглашение ?- и ждёт вопрос. Самый простой запрос — спросить про конкретный факт:

?- parent(tom, bob).

Вывод:

true.

Prolog просмотрел стопку фактов parent/2, нашёл точное совпадение и ответил true — «да, это правда». А если спросить про то, чего в базе нет?

?- parent(liz, bob).

Вывод:

false.

Такого факта в базе нет, вывести его тоже неоткуда, поэтому ответ — false. Важная оговорка: false в Prolog значит не «это ложь во вселенной», а «я не смог это доказать по своей базе». Это называют предположением о замкнутости мира: чего нет в базе и что нельзя вывести — считается ложным.

Запросы с переменными

Спрашивать «да/нет» — только половина возможностей. Куда интереснее спросить «а кто?». Для этого в запрос подставляют переменную — идентификатор с заглавной буквы. Спросим, кто дети Тома:

?- parent(tom, X).

Вывод:

X = bob.

Prolog нашёл первый факт, который подходит под образец parent(tom, X) — это parent(tom, bob) — и связал переменную X со значением bob. Такую связку «переменная = значение» называют подстановкой (или связыванием, binding). Ответ X = bob читается как: «Утверждение истинно, если X равно bob».

Перебор решений: ; и Enter

Но у Тома два ребёнка! Где же liz? Дело в том, что Prolog показывает по одному решению за раз и ждёт вашей реакции. Если вы хотите следующее решение, нажмите точку с запятой ; (или клавишу пробел/«n» в некоторых системах). Если первого решения достаточно — нажмите Enter, и поиск остановится.

?- parent(tom, X).
X = bob ;
X = liz ;
false.

Разберём по шагам, что произошло после нажатий ;:

ДействиеОтвет Prolog
запросX = bob — первое решение
нажали ;X = liz — второе решение
нажали ;false — больше нет

Финальное false означает не ошибку, а «других решений не осталось». Таким образом Prolog способен перебрать все факты, подходящие под образец, — нужно лишь продолжать запрашивать следующее.

Переменная в роли родителя

Переменную можно поставить в любую позицию. Спросим, кто родители Боба и Лиз:

?- parent(Y, bob).

Вывод:

Y = tom.

А можно поставить переменные сразу в обе позиции — тогда Prolog выдаст все пары «родитель — ребёнок» подряд:

?- parent(P, C).
P = tom, C = bob ;
P = tom, C = liz ;
P = bob, C = ann ;
P = bob, C = pat ;
false.

Как работает под капотом

За ответами стоит простой, но мощный механизм. Получив запрос parent(tom, X), Prolog берёт его как образец (цель) и идёт по стопке фактов parent/2 сверху вниз. Для каждого факта он пытается выполнить унификацию — сопоставить образец с фактом так, чтобы они совпали.

Унификация parent(tom, X) с фактом parent(tom, bob) удаётся: tom совпадает с tom, а свободная переменная X связывается с bob. Prolog запоминает это место в стопке (ставит «закладку», которую называют точкой выбора, choice point) и выдаёт первое решение.

Когда вы нажимаете ;, срабатывает откат (backtracking): Prolog возвращается к закладке, развязывает X и продолжает идти вниз по стопке от того же места. Следующий подходящий факт — parent(tom, liz) — даёт X = liz. Когда подходящих фактов больше нет, дальнейший откат возвращает false. Этот цикл «унификация → решение → откат» — сердце выполнения в Prolog.

?- parent(tom, X)
   │
   ├─ факт parent(tom, bob)  →  X = bob   (закладка)
   │      ↑ нажали ;  (откат)
   ├─ факт parent(tom, liz)  →  X = liz
   │      ↑ нажали ;  (откат)
   └─ больше нет             →  false

Частые ошибки

  • Переменная со строчной буквы. parent(tom, x) — это вопрос «верно ли, что ребёнок Тома именно атом x?», ответ false. Чтобы спросить «кто?», нужна X с заглавной.
  • Ожидание всех ответов сразу. Prolog показывает решения по одному; забыв нажать ;, легко решить, что ребёнок у Тома только один.
  • Паника от финального false. false после серии решений — это нормальный сигнал «больше нет», а не сбой.
  • Забытая точка в запросе. Как и факт, запрос завершается точкой; без неё toplevel будет ждать продолжения.
  • Неверная позиция переменной. Перепутав местами родителя и ребёнка (parent(X, tom) вместо parent(tom, X)), вы спросите не то, что хотели.

Итоги

  • Запрос начинается с ?- и заканчивается точкой; он спрашивает базу знаний.
  • Факт без переменных даёт ответ true (найден/выведен) или false (не доказан).
  • Переменная (с заглавной буквы) превращает вопрос «да/нет» в вопрос «кто/что», а ответ — в подстановку X = ....
  • Решения выдаются по одному: ; запрашивает следующее, Enter останавливает поиск.
  • Под капотом работают унификация и откат (backtracking), а итоговое false значит «решений больше нет».
Проверьте себя
1. Что ответит Prolog на запрос ?- parent(liz, bob). если такого факта в базе нет и вывести его нельзя?
Atrue
Bfalse
Cошибку синтаксиса
Dliz = bob
2. Зачем в запросе parent(tom, X) переменная X пишется с заглавной буквы?
AЧтобы Prolog воспринял X как переменную и подобрал для неё значение
BЭто просто стиль оформления, можно и со строчной
CЧтобы ответ выводился крупным шрифтом
DЗаглавная буква обозначает константу-атом
3. Вы получили X = bob и хотите увидеть следующее решение. Что нужно сделать?
AНажать Enter
BПерезапустить программу
CНажать точку с запятой ;
DВвести запрос заново
4. Что означает финальное false в выводе X = bob ; X = liz ; false?
AПроизошла ошибка выполнения
BВсе предыдущие ответы были неверны
CПодходящих решений больше не осталось
DБаза знаний повреждена