В качестве небольшой части фона мы можем рассматривать односвязный список, состоящий из двух «конструкторов»: []
, пустой список и [H|T]
, где H
- это какое-то значение, присутствующее в заголовок списка и T
, хвост, список того, что осталось после H.
Здесь есть два пункта. Первый такой:
delete(A, [A|B], B).
Я бы, вероятно, сформулировал это немного иначе, как delete(Head, [Head|Tail], Tail)
. Это пункт, который позволяет унификации, такие как:
?- delete(3, [3,a,b,c,d], X).
X = [a, b, c, d]
Это работает, потому что [H|T]=[3,a,b,c,d]
объединит H=3
и T=[a,b,c,d]
. Попробуй.
Теперь давайте на минутку подумаем о том, как Пролог выбирает предложение. На каждом этапе Пролог по сути пытается объединиться. Поэтому, когда ему присваивается delete(a, [1,2,3,a,b,c,d], X)
, Пролог пытается объединить delete(H, [H|T], T)
с delete(a, [1,2,3,a,b,c,d], X)
. Это не может быть успешным, потому что a! = 1. Таким образом, Пролог делает еще одну попытку со вторым предложением, и мы оказываемся здесь:
delete(A, [B, C|D], [B|E]) :-
delete(A, [C|D], E).
Теперь на примере Пролог попытается объединить заголовок предложения с delete(A=a, [B=1,C=2|D=[3,a,b,c,d]], [B=1|E])
. Это успешно происходит, потому что нет особой причины, по которой A
не может равняться a, B
не может равняться 1, C
не может равняться 2 и D
не может равняться [3,a,b,c,d]
. Итак, теперь Пролог переходит на delete(A, [C|D], E)
и попытается вызвать delete(a, [2,3,a,b,c,d], E)
. Если вы включите trace
в своем REPL, вы увидите это на работе:
?- trace.
true.
[trace] ?- delete(a, [1,2,3,a,b,c,d], X).
Call: (8) delete(a, [1, 2, 3, a, b, c, d], _3240) ? creep
Call: (9) delete(a, [2, 3, a, b, c, d], _3498) ? creep
По второму вызову вы видите, что Пролог теперь заинтересован в поиске delete(a, [2,3,a,b,c,d], _4120)
, и это новая переменная, а не переменная из предыдущего вызова. Я могу заверить вас, что то же самое случится еще несколько раз, прежде чем случится что-то новое.
Call: (9) delete(a, [2, 3, a, b, c, d], _3498) ? creep
Call: (10) delete(a, [3, a, b, c, d], _3510) ? creep
Однако при следующем вызове можно активировать первое предложение:
Call: (11) delete(a, [a, b, c, d], _3522) ? creep
Exit: (11) delete(a, [a, b, c, d], [b, c, d]) ? creep
На этот раз переменная была объединена с чем-то. Это потому, что ваше первое предложение является базовым. Теперь, оглядываясь назад на свое определение, вы можете видеть, что третий аргумент во втором предложении - [B|E]
, что означает, что он получит значение, когда оба B
и E
были объединены с чем-то. Таким образом, оставшаяся часть трассировки - это выходы из этого предложения с этой переменной, объединенной с чем-то. Каждый раз, что происходит, это просто B
, который был удален из списка до того, как рекурсивный вызов был добавлен к результату:
Exit: (10) delete(a, [3, a, b, c, d], [3, b, c, d]) ? creep
Exit: (9) delete(a, [2, 3, a, b, c, d], [2, 3, b, c, d]) ? creep
Exit: (8) delete(a, [1, 2, 3, a, b, c, d], [1, 2, 3, b, c, d]) ? creep
X = [1, 2, 3, b, c, d] .
И вот оно у вас.