Нотация списков [H|T]

Список в Prolog — это рекурсивная структура из головы и хвоста, и почти вся работа со списками строится на одной нотации [H|T].

Список — упорядоченная последовательность элементов; в Prolog это либо пустой список [], либо пара «голова и хвост», записываемая как [H|T].

Если в большинстве языков список — это массив с индексами, то в Prolog список — это вложенная структура, очень похожая на односвязный список. Понимать его устройство критично: без чёткого образа того, что такое голова и хвост, рекурсивные предикаты будут казаться магией. Давайте разберём нотацию до самого дна.

Зачем нужна особая нотация

Списки встречаются буквально везде: разобрать строку, перебрать кандидатов, накопить результат, представить множество фактов. Prolog даёт компактный синтаксис, который одновременно служит и для построения списка, и для его разбора. Одна и та же запись [H|T] в одном месте программы соберёт список из головы и хвоста, а в другом — расщепит существующий список на первый элемент и остаток. Эта двойственность и делает Prolog таким выразительным.

Из чего состоит список

Самый простой список — пустой: []. Это атом, базовый кирпич, на котором заканчивается любая рекурсия. Непустой список записывают, перечисляя элементы в квадратных скобках:

[]                  % пустой список
[1]                 % список из одного элемента
[1, 2, 3]           % список из трёх чисел
[a, b, c]           % список из атомов
[1, foo, [2,3], X]  % элементы могут быть любыми термами

Элементы могут быть чем угодно: числами, атомами, переменными и даже другими списками. Список — это контейнер для произвольных термов.

Голова и хвост

Главная нотация — вертикальная черта: [H|T]. Слева от черты — голова (первый элемент), справа — хвост (список из оставшихся элементов). Хвост — это всегда список, даже если он пустой.

[1, 2, 3]  это то же самое, что  [1 | [2, 3]]
[2, 3]     это то же самое, что  [2 | [3]]
[3]        это то же самое, что  [3 | []]

Видно матрёшку: каждый список — это голова плюс хвост, который сам устроен так же, пока мы не упрёмся в []. Можно перечислить несколько голов сразу: [A, B | T] берёт первые два элемента и остаток.

Сопоставление списков по образцу

Унификация делает нотацию [H|T] рабочим инструментом. Когда Prolog сопоставляет список с образцом, он раскладывает его на части и связывает переменные. Разберём на запросах:

?- [X | T] = [10, 20, 30].
X = 10,
T = [20, 30].

?- [X, Y | _] = [10, 20, 30].
X = 10,
Y = 20.

?- [_, _, Z | _] = [10, 20, 30, 40].
Z = 30.

?- [X] = [10, 20, 30].
false.

Разберём каждый случай. Образец [X|T] вытаскивает голову в X и весь хвост в T. Образец [X, Y | _] берёт первые два элемента, а остаток отбрасывает в анонимную переменную _ (нам неважно, что там). Образец [_, _, Z | _] пропускает два элемента и связывает третий. А вот [X] = [10, 20, 30] терпит неудачу: образец [X] — это список ровно из одного элемента (то есть [X | []]), а справа три элемента, хвост не может унифицироваться с [].

Пустой список как образец

Отдельно стоит образец []. Он сопоставляется только с пустым списком и ни с чем больше. Это та самая граница, где рекурсия останавливается:

?- [] = [].
true.

?- [] = [1].
false.

?- [H | T] = [].
false.

Запомните пару фактов: [] унифицируется только с [], а образец [H|T] требует непустого списка — у пустого списка нет головы. Именно поэтому базовый случай предиката почти всегда работает с [], а рекурсивный шаг — с [H|T].

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

Квадратные скобки — это синтаксический сахар. На самом деле список [1, 2, 3] внутри Prolog хранится как вложенная структура из функтора с именем '[|]' (в старых системах — '.') и двумя аргументами: голова и хвост.

[1, 2, 3]  внутри это  '[|]'(1, '[|]'(2, '[|]'(3, [])))

То есть [H|T] — это просто читаемая запись для '[|]'(H, T). Можно нарисовать дерево:

   '[|]'
   /   \
  1   '[|]'
      /   \
     2   '[|]'
         /   \
        3    []

Эта структура объясняет всё: почему доступ к голове мгновенный (она в корне), а доступ к последнему элементу требует рекурсивного спуска до самого низа. Список — это вложенные пары, и унификация просто сопоставляет деревья узел за узлом. Понимая это представление, вы перестаёте удивляться, почему append работает за линейное время, а добавление в начало — за константное.

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

Первая и самая частая: путать запятую и вертикальную черту. [H, T] — это список из ровно двух элементов, а [H | T] — голова и хвост-список. Эти образцы ведут себя совершенно по-разному, и подмена ломает рекурсию.

Вторая ошибка: ждать, что хвост — это элемент. Хвост всегда список. В [1 | T] для списка [1, 2, 3] переменная T станет [2, 3], а не 2. Чтобы получить именно второй элемент, нужен образец [_, X | _].

Третья ошибка: применять образец [H|T] к пустому списку и удивляться неудаче. Это не баг — у [] просто нет головы. Всегда предусматривайте отдельное правило для [].

Итог

  • Список — это либо [], либо пара [H|T] из головы и хвоста-списка.
  • Нотация [H|T] служит и для сборки, и для разбора списка через унификацию.
  • Образцы вроде [X|_], [X,Y|_], [_,_,Z|_] вытаскивают нужные элементы по позиции.
  • [] унифицируется только с пустым списком; [H|T] требует непустого.
  • Под капотом список — вложенная структура '[|]'(H, T), заканчивающаяся на [].
Проверьте себя
1. С чем унифицируется образец [X | T] при сопоставлении со списком [a, b, c]?
AX = a, T = [b, c]
BX = a, T = b
CX = [a, b], T = c
DУнификация терпит неудачу
2. Почему запрос [X] = [1, 2, 3] завершается неудачей?
AПотому что X не может быть числом
BПотому что [X] — это список ровно из одного элемента, а справа их три
CПотому что нужно писать [X|_] всегда
DПотому что списки нельзя сравнивать через =
3. Как внутри Prolog представлен список [1, 2]?
AКак массив из двух ячеек
BКак '[|]'(1, '[|]'(2, []))
CКак строка "12"
DКак пара (1, 2)