Анонимная переменная и сопоставление
Урок про подчёркивание _ — способ сказать Prolog «здесь что-то есть, но мне всё равно что».
Анонимная переменная (
_) — это переменная, которая унифицируется с любым термом, но не запоминает значение и не связывается с другими вхождениями_.
Когда вы пишете правила и факты, часто оказывается, что некоторые аргументы вам нужны лишь как «заполнитель»: они должны там быть для совпадения по форме, но само значение вас не интересует. Для этого в Prolog есть анонимная переменная. Без неё код пухнет от ненужных имён, а интерпретатор сыплет предупреждениями.
Зачем это нужно
Представьте факты о людях: person(ann, 30, moscow) — имя, возраст, город. Допустим, вам нужны только имена всех людей, а возраст и город безразличны. Придумывать им имена Age, City — лишняя работа, да ещё Prolog предупредит: «singleton variable» (переменная использована один раз — может, опечатка?). Анонимная переменная решает обе проблемы: коротко и без предупреждений.
Как пишется _
Одиночное подчёркивание _ — это анонимная переменная. Она унифицируется с чем угодно, но её значение никуда не сохраняется.
?- person(Name, _, _) = person(ann, 30, moscow).
Name = ann. % имя получили, возраст и город отброшены
Здесь Name мы попросили вернуть, а на месте возраста и города поставили _ — «неважно». Сравните с альтернативой, где пришлось бы ввести Age и City и потом их игнорировать.
Каждое _ — независимая переменная
Ключевой и неочевидный момент: разные вхождения _ в одном правиле — это РАЗНЫЕ переменные. Они не связаны между собой, в отличие от именованных переменных.
?- pair(_, _) = pair(1, 2).
true. % первое _ стало 1, второе _ стало 2 — конфликта нет
?- pair(X, X) = pair(1, 2).
false. % X должен быть и 1, и 2 одновременно — невозможно
В первом запросе два _ независимы, поэтому 1 и 2 спокойно «улеглись». Во втором одна и та же X обязана быть равной и 1, и 2 — а это противоречие, отсюда false. Вот почему важно различать «мне всё равно для каждой позиции отдельно» (_) и «здесь должно быть одно и то же значение» (X).
Паттерн-матчинг по структуре
Унификация с _ — основа сопоставления с образцом. Вы описываете форму терма, фиксируете нужное и игнорируете остальное.
% факты: книга(Название, Автор, Год, Жанр)
book('Война и мир', tolstoy, 1869, roman).
book('Идиот', dostoevsky, 1869, roman).
book('Метель', pushkin, 1831, povest).
% правило: автор любой книги 1869 года
author_1869(Author) :- book(_, Author, 1869, _).
В правиле author_1869 название и жанр заменены на _ (неважно), год жёстко зафиксирован числом 1869 (должен совпасть), а автор связывается с Author (его и вернём). Запрос отработает так:
?- author_1869(A).
A = tolstoy ;
A = dostoevsky.
Вывод:
A = tolstoy ; A = dostoevsky.
Книга Пушкина не подошла — год 1831 не унифицировался с 1869, и Prolog её отбросил.
Списки и _
Особенно удобно с _ работать со списками. Например, «первый элемент списка» — нам неважен хвост:
first(X, [X|_]). % голова — это X, хвост неважен
?- first(F, [10, 20, 30]).
F = 10.
second(X, [_, X|_]). % первый неважен, второй — это X, остаток неважен
?- second(S, [10, 20, 30]).
S = 20.
Конструкция [X|_] читается как «список, у которого голова X, а хвост — какой угодно». Здесь _ избавляет от ненужного имени для хвоста.
Как работает под капотом
Когда парсер Prolog встречает _, он создаёт новую свежую переменную при каждом вхождении. То есть foo(_, _) внутри превращается в нечто вроде foo(_G1, _G2) с разными внутренними именами. Поэтому два _ и не связаны. Дальше эта свежая переменная унифицируется как обычно, но поскольку на неё больше никто не ссылается, её связанное значение немедленно становится «мусором» — отсюда и то, что значение «не сохраняется».
first(F, [10, 20, 30])
│
сопоставляем с first(X, [X|_])
│
F = X, [10,20,30] = [X|_]
│
X = 10 (голова), _ = [20,30] (хвост — отброшен)
│
F = 10
А именованная переменная, использованная лишь раз (например Tail в [X|Tail], где Tail больше нигде нет), вызывает предупреждение «singleton». Prolog подсказывает: «возможно, опечатка — может, ты хотел _?» Если значение правда не нужно — меняйте на _ или на _Tail (имя с подчёркивания тоже глушит предупреждение, но при этом читается).
Частые ошибки
- Думать, что все
_равны.p(_, _)— это НЕp(X, X). Каждое подчёркивание независимо; чтобы потребовать одинаковости, нужна именованная переменная. - Пытаться использовать значение
_. Из_ничего не вернуть — оно безымянно. Если значение нужно дальше, дайте переменной имя. - Игнорировать предупреждение singleton. Оно часто ловит реальные опечатки в именах. Осознанно неиспользуемую переменную помечайте
_или префиксом_. - Ставить
_там, где нужна связь. В правиле «у X и Y один родитель» обе позиции родителя должны быть одной переменной, а не двумя_.
Итоги
_— анонимная переменная: унифицируется с чем угодно, значение не сохраняется.- Каждое вхождение
_— отдельная свежая переменная; они между собой не связаны. _— инструмент паттерн-матчинга: фиксируем нужные позиции, игнорируем ненужные.- В списках
[X|_]удобно брать голову, не называя хвост. - Предупреждение «singleton variable» — повод заменить одноразовую переменную на
_(если значение действительно не нужно).