Отрицание как неудача (\+)
Урок про оператор \+ — способ сказать «это недоказуемо» и про подводные камни такого «отрицания».
Отрицание как неудача (
\+ Goal) — цель, которая успешна тогда и только тогда, когдаGoalне удаётся доказать из текущей базы знаний.
В логике мы привыкли к отрицанию «неверно, что...». Но Prolog не знает абсолютной истины — он знает лишь то, что записано в его базе. Поэтому его «не» — особое: оно означает не «ложно», а «не смог доказать». Это маленькое, но принципиальное отличие меняет многое.
Как пишется и работает
Оператор \+ ставится перед целью. Если Goal доказывается — \+ Goal проваливается. Если Goal доказать не удалось — \+ Goal успешно.
likes(mary, wine).
likes(mary, books).
?- \+ likes(mary, beer).
true.
?- \+ likes(mary, wine).
false.
Вывод:
true.
Про beer в базе ничего нет, доказать likes(mary, beer) нельзя — значит \+ успешно. А likes(mary, wine) — факт, он доказывается, поэтому \+ likes(mary, wine) проваливается.
Замкнутость мира
За этим стоит предположение о замкнутости мира (closed-world assumption): всё, что не выводится из базы, считается ложным. Prolog исходит из того, что база знаний полна — если факта нет, его как бы не существует.
В реальности это не всегда так: отсутствие записи может означать «неизвестно», а не «ложно». Из-за этого \+ уместен там, где база действительно полна (например, список зарегистрированных пользователей), и опасен там, где данные неполны.
Отличие от логического отрицания
Логическое «не P» утверждает, что P объективно ложно. \+ P утверждает лишь, что P не доказуемо здесь и сейчас. Разница хорошо видна на динамике: добавьте факт — и тот же запрос \+ поменяет ответ, хотя «истина» во внешнем мире не менялась.
| Свойство | Логическое ¬P | \+ P в Prolog |
| Смысл | P ложно | P не доказуемо из базы |
| Зависит от базы | нет | да |
| Связывает переменные | — | никогда не оставляет связок |
Ловушка: \+ с переменными
Самая коварная ошибка — отрицание цели с несвязанными переменными. \+ никогда не возвращает связок переменных: даже когда внутренняя цель находит решение, после отката все связки теряются. Поэтому смысл «не существует такого X» работает корректно, а вот «найди X, которого нет» — нет.
student(anna).
student(boris).
?- \+ student(X).
false.
?- \+ student(carol).
true.
Вывод:
false.
Запрос \+ student(X) читается как «не существует студента» — а студенты есть, поэтому false. Если вы хотели «X не является студентом», нужно сперва связать X конкретным значением, и только потом отрицать: X = carol, \+ student(X). Правило: к моменту \+ переменные внутри должны быть означены, иначе результат почти наверняка не тот, что вы ждёте.
Как работает под капотом
Классически \+ Goal определяется через связку cut-fail:
neg(Goal) :- call(Goal), !, fail.
neg(_).
Логика такая: пробуем доказать Goal через call. Если получилось — срабатывает ! (отсекает второе предложение) и затем fail заставляет весь neg провалиться. Если же Goal не доказался, call провалился до cut, и тогда сработает второе предложение neg(_), давая успех.
Goal доказан? ├─ да -> ! , fail -> neg ПРОВАЛ └─ нет -> второе правило -> neg УСПЕХ
Именно поэтому \+ не оставляет связок: любые подстановки, найденные внутри call(Goal), откатываются вместе с fail. Понимание этой схемы объясняет и ловушку с переменными, и связь отрицания с отсечением.
Частые ошибки
- Отрицание со свободными переменными.
\+ p(X)при несвязанномXпочти всегда даёт не тот смысл. Сначала свяжите переменные. - Ожидать связок от
\+. Отрицание не возвращает значений — оно только успех или неудача. - Применять
\+к неполной базе. «Не доказано» не равно «ложно»; на неполных данных это даёт ложноположительные ответы. - Путать с
\=.\=— это «термы не унифицируются», узкий частный случай;\+— общее отрицание любой цели.
Итог
\+ Goalуспешно тогда, когдаGoalнедоказуемо из базы.- В основе — предположение о замкнутости мира: чего нет в базе, то считается ложным.
- Это «не доказуемо», а не логическое «ложно» — ответ зависит от содержимого базы.
\+требует означенных переменных и никогда не возвращает связок.- Классически реализуется через cut-fail:
call(Goal), !, fail.плюс запасное правило.