Пролог не завершается после изменения порядка целей

В настоящее время я работаю над примерами Learn Prolog Now и над одним упражнением У меня есть КБ, в котором заканчивается локальный стек, если я просто внесу небольшое изменение в одно правило. это КБ:

byCar(auckland,hamilton). 
byCar(hamilton,raglan). 
byCar(valmont,saarbruecken). 
byCar(valmont,metz). 

byTrain(metz,frankfurt). 
byTrain(saarbruecken,frankfurt). 
byTrain(metz,paris). 
byTrain(saarbruecken,paris). 

byPlane(frankfurt,bangkok). 
byPlane(frankfurt,singapore). 
byPlane(paris,losAngeles). 
byPlane(bangkok,auckland). 
byPlane(singapore,auckland). 
byPlane(losAngeles,auckland).

travel(X,Y) :- byCar(X,Y).
travel(X,Y) :- byTrain(X,Y).
travel(X,Y) :- byPlane(X,Y).

и соответствующее правило:

travel(X,Y) :- travel(X,Z), travel(Z,Y).

и это рассматриваемый запрос, который выходит за пределы стека:

?- travel(valmont,losAngeles).

Но если я изменю правило на

travel(X,Y) :- travel(Z,Y), travel(X,Z).

Тогда это работает.

Если я отслеживаю запрос, я быстро застреваю следующим образом:

   Redo: (17) travel(raglan, _6896) ? creep
   Call: (18) byPlane(raglan, _6896) ? creep
   Fail: (18) byPlane(raglan, _6896) ? creep
   Redo: (17) travel(raglan, _6896) ? creep
   Call: (18) travel(raglan, _6896) ? creep
   Call: (19) byCar(raglan, _6896) ? creep
   Fail: (19) byCar(raglan, _6896) ? creep
   Redo: (18) travel(raglan, _6896) ? creep
   Call: (19) byTrain(raglan, _6896) ? creep
   Fail: (19) byTrain(raglan, _6896) ? creep
   Redo: (18) travel(raglan, _6896) ? creep
   Call: (19) byPlane(raglan, _6896) ? creep
   Fail: (19) byPlane(raglan, _6896) ? creep
   Redo: (18) travel(raglan, _6896) ? creep
...

Но я не понимаю, почему. Разве он не должен просто понять, что реглан — это конечная станция, и поэтому он должен вернуться еще на один уровень?

Спасибо!

Изменить: я использую SWI Prolog

Правка: я обнаружил проблему после пошагового ее изучения. В случае с регланом вообще нет правила никуда. Следовательно, после попытки byPlane, byTrain, byCar он снова пытается travel(raglan, X) (первая цель последнего правила), таким образом, зацикливается. Но я не вижу, чем другое правило лучше.


person Aurel Gruber    schedule 04.03.2019    source источник


Ответы (2)


Очевидно, что порядок целей действительно важен в этом случае. Как вы поняли, ваша первая формулировка позволяет найти еще одну гипотетическую связь от реглана с чем угодно, пройдя через гипотетический другой город Z, несуществование которого никогда не доказывается, потому что вы продолжаете искать его, рекурсивно бесконечно. На самом деле, трассировка — ваш лучший друг, но это не так просто сделать правильно. Вы также должны подумать обо всех случаях, когда один, оба или ни один из аргументов не связаны.

Ваша вторая формулировка вовсе не лучше, просто в разных случаях она не работает:

travel(losAngeles, valmont).
ERROR: Out of local stack

Я бы предложил сделать вашу логику более безопасной, разграничив прямое соединение и путешествие с несколькими остановками:

connection(X,Y) :- byCar(X,Y).
connection(X,Y) :- byTrain(X,Y).
connection(X,Y) :- byPlane(X,Y).

travel(X,Y) :- connection(X,Y).
travel(X,Y) :- connection(X,Z), travel(Z,Y).

Порядок целей теперь не имеет значения, потому что travel всегда требует существования некоторого физического соединения (а не рекурсии), чтобы продолжить.

Это также упрощает запись путешествия, которое вы бы в любом случае хотели (верно?):

connection(X,Y, car(X,Y))   :- byCar(X,Y).
connection(X,Y, train(X,Y)) :- byTrain(X,Y).
connection(X,Y, plane(X,Y)) :- byPlane(X,Y).

travel(X,Y,[Part])       :- connection(X,Y,Part).
travel(X,Y,[Part|Parts]) :- connection(X,Z,Part), travel(Z,Y,Parts).

?- travel(valmont, losAngeles, Journey).
Journey = [car(valmont, saarbruecken), train(saarbruecken, paris), plane(paris, losAngeles)] 

И для случая, когда нет действительной поездки:

travel(losAngeles, valmont, Journey).
false.
person firefrorefiddle    schedule 04.03.2019
comment
Это имеет большой смысл! Это также прекрасно вписывается в следующую главу о списках :) Спасибо! - person Aurel Gruber; 04.03.2019

Вам нужно уточнить, что вы подразумеваете под «это работает». На самом деле обе версии предиката travel/2 не заканчиваются. Но случается найти решение для очень конкретного запроса.

Теперь спросите ?- travel(hamilton, losAngeles)., какая петля для обоих.

Таким образом, ваше исправление работает только для некоторых запросов, но не для других. Неужели нет более надежного выхода?

В общем, очень точную последовательность подстановок ответов, производимых Прологом, трудно предсказать. Вам придется моделировать каждый крошечный шаг, который делает Пролог.

С другой стороны, есть очень связанное понятие, называемое (универсальным) завершением, которое гораздо легче предсказать, поскольку оно не зависит от многих деталей в вашей программе, таких как порядок, в котором появляются ваши факты. Самый простой способ запросить универсальное прекращение — добавить цель false в конце запроса.

Но вы можете пойти еще дальше, добавляя цели false в любом месте1. Такая модифицированная программа называется

closure/3. То есть:

travel(X, Y) :-
   closure(connexion, X, Y).

connexion(X,Y) :- byCar(X,Y).
connexion(X,Y) :- byTrain(X,Y).
connexion(X,Y) :- byPlane(X,Y).

Или используйте более общий path/4.


1 На самом деле это работает только в чисто монотонных программах. Ваша программа — одна из таких

person false    schedule 04.03.2019