Как [ Ч | _ ] и [ _ | T ] в предикатах работает?

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

Код:

% Takes the spiders friends and returns a list with persons who don't know each other.
getConspirators( [], Res, Res).

getConspirators( [H|T], CConspirators, Res):-
  append( [H|T], CConspirators, PK),
  knowsAtleastOne( PK),
  % Gets all the friends of the possible conspirator H.
  allFriends( H, PFriends),
  subtract( T, PFriends, Pprim),
  getConspirators( Pprim, [H|CConspirators], Res).

getConspirators( [_|T], CConspirators, Res) :-
  getConspirators( T, CConspirators, Res).

% Checks if any person Y or Y's friends know anybody in PK.
knowsAtleastOne( PK):-
    forall( person(Y), (memberchk(Y,PK) ; friendCons(Y,PK)) ).

% Checks if a person X's friends know any of the conspirators.
friendCons( X, Conspirators):-
    friend( X, Y),
    memberchk( Y, Conspirators),
    !.

(это НЕ вся программа, просто ее небольшой фрагмент)

Я не уверен, понял ли я части getConspirators( [H|T], CConspirators, Res) :- и getConspirators( [_|T], CConspirators, Res) :- предиката getConspirators. Они выглядят почти одинаково! Теперь я знаю, что символ _ означает буквально любое значение (AKA Prolog не заботится о том, какое это значение). Но как Пролог узнает, какой регистр выбрать при прогоне кода? Моя теория состоит в том, что Пролог запускает случай getConspirators( [_|T], CConspirators, Res) :- тогда и только тогда, когда случай getConspirators( [H|T], CConspirators, Res) :- терпит неудачу (возвращает false) где-то по пути. Я правильно это понял?


person Schytheron    schedule 03.12.2017    source источник
comment
Нет, Пролог работает с поиском с возвратом, но он делает это независимо от того, работает или не работает предыдущее предложение (кроме случаев, когда вы используете некоторые вещи, такие как cut !).   -  person Willem Van Onsem    schedule 03.12.2017
comment
@WillemVanOnsem Можете ли вы объяснить немного подробнее? Я новичок в Прологе :)   -  person Schytheron    schedule 03.12.2017
comment
Я думаю, что простое объяснение этого фрагмента кода сложно без каких-либо знаний о более простых предикатах Prolog. Пролог немного уникален как язык программирования: он декларативен, имеет встроенный поиск с возвратом, предикаты работают в нескольких направлениях, и сочетание всех этих функций, как правило, трудно понять.   -  person Willem Van Onsem    schedule 03.12.2017
comment
@WillemVanOnsem Итак, если бы часть getConspirators([H|T], CConspirators, Res):- была вырезана! перед его рекурсивным вызовом (имеет ли значение, где вы его поместите?) он остановит выполнение кода там, если что-то выйдет из строя во время выполнения, и перейдет к следующему предложению (getConspirators([_|T], CConspirators, Res) :-)? Но без разреза (как здесь) последний пункт будет запускаться каждый раз после первого, независимо от того, что происходит? Итак, если все это правильно, задача второго предложения состоит в том, чтобы просто перейти к следующему элементу в списке (например, цикл for)?   -  person Schytheron    schedule 03.12.2017
comment
нет, если вызовы предиката внутри предложения getConspirators([H|T], CConspirators, Res) терпят неудачу до достижения разреза !, разрез не будет выполнен. Если вы выполните отсечение, вы не будете учитывать оставшиеся предложения этого предиката в этом рекурсивном вызове (ни глобально, ни в рекурсивных вызовах, исходящих из этого вызова).   -  person Willem Van Onsem    schedule 03.12.2017
comment
@WillemVanOnsem Итак, какова реальная цель предиката getConspirators([_|T], CConspirators, Res) :- (последнего). Почему это там?   -  person Schytheron    schedule 03.12.2017
comment
Начните с намного программ меньшего размера.   -  person false    schedule 03.12.2017


Ответы (2)


Здесь задействованы три элемента: поиск с возвратом, унификация и нотация списка. Я объясню три на более простом примере:

moon(europa).
moon(ganymede).

planet(jupiter).
planet(saturn).

Мы знаем, что Европа и Ганимед — две луны (Юпитера), а Юпитер и Сатурн — планеты. Когда мы спрашиваем, какие планеты известны, мы пишем:

?- planet(X).
X = jupiter ;  % type ; for the next answer
X = saturn.    % there's no more answer, hence .

Унификация происходит, когда пролог ищет заголовок правила, который соответствует запросу, в котором переменные заменяются соответствующим образом. Например, нет замены, которая делает moon(X) = planet(Y) равным, но есть замена для planet(jupiter) = planet(X), а именно X=jupiter. Вот как вы получаете первое решение. Для второго решения Пролог должен унифицироваться со вторым заголовком правила, а именно planet(saturn) = planet(X). Поскольку это делается после полного перечисления первого варианта, мы называем это возвратом.

Теперь мы можем сосредоточиться на (связанных) списках. Список либо пуст ([]), либо имеет первый элемент X, добавленный к хвостовому списку Xs ([X|Xs]). В Прологе для списка [X | [Y | [] ]] также имеется более удобное обозначение, а именно [X,Y]. Внутренне они одинаковы. Теперь, когда мы хотим составить список астральных объектов, мы можем сформулировать следующие три правила:

astral_objects([]).        % The empty list is a list of astral objects.

astral_objects([X|Xs]) :-  % The list [X | Xs] is a list of astral objects if...
    moon(X),               % ... its first element X is a moon
    astral_objects(Xs).    % ... and the remaining list Xs is a list of astral objects

astral_object([X|Xs]) :-   % Likewise for planets
    planet(X),
    astral_objects(Xs).

Когда мы формулируем запрос для двухэлементного списка, мы получаем все комбинации объектов:

?- astral_object([A,B]).
A = B, B = europa ;
A = europa,
B = ganymede ;
A = europa,
B = jupiter ;
A = europa,
B = saturn ;
A = ganymede,
B = europa ;
A = B, B = ganymede ;
A = ganymede,
B = jupiter
%...

При объединении применяются только правила 2 и 3. В обоих случаях у нас есть astral_objects([X|Xs]) = astral_objects([A,B]). Помните, что [A,B] — это сокращение от [A|[B]], а также X=A и Xs=[B]. Первое правило тела объединит X с соответствующей луной/планетой, а шаг рекурсии описывает хвост. Мы снова объединяем astral_objects([X|Xs]) = astral_objects([B]), что приводит к X=B и Xs = []. Теперь шаг рекурсии будет соответствовать только терминальному случаю пустого списка, и мы полностью изучили этот путь.

Что произойдет, если мы найдем произвольный список астральных объектов?

?- astral_object(Xs).
Xs = [] ;
Xs = [europa] ;
Xs = [europa, europa] ;
Xs = [europa, europa, europa] ;
Xs = [europa, europa, europa, europa] ;
Xs = [europa, europa, europa, europa, europa] 
%... does not terminate

Голова astral_objects(Xs) соответствует всем трем телам. После возврата замены для терминального падежа он снова и снова спускается к первому правилу. Поскольку длина списка не ограничена, существует бесконечное количество решений, которые нужно найти, прежде чем будет применено третье правило. Чтобы избежать этого, вы можете честно перечислить списки, прежде чем пытаться заставить их удовлетворять предикату:

?- length(Xs,_), astral_object(Xs).
Xs = [] ;
Xs = [europa] ;
Xs = [ganymede] ;
Xs = [jupiter] ;
Xs = [saturn] ;
Xs = [europa, europa] ;
Xs = [europa, ganymede] ;
Xs = [europa, jupiter] ;
Xs = [europa, saturn] ;
Xs = [ganymede, europa]
%...

Он по-прежнему не завершается, но вы видите списки по возрастанию длины и, следовательно, разнообразие.

person lambda.xy.x    schedule 04.12.2017

заданный вопрос был "части getConspirators([H|T], CConspirators, Res) :- _body_ и getConspirators([_|T], CConspirators, Res) :- _body_ ... Моя теория состоит в том, что Пролог запускает getConspirators([_|T], CConspirators, Res) :- case тогда и только тогда, когда getConspirators([H|T], CConspirators , Res) :- case не пройден (возвращает false)"

Ваша теория неверна. Оба они будут соответствовать. Единственное отличие состоит в том, что в случае getConspirators([H|T], CConspirators, Res) :- _body_ первый элемент списка будет доступен в body как переменная с именем H . Но для getConspirators([_|T], CConspirators, Res) :- _body_ первый элемент списка не будет доступен в body как именованная переменная. Хороший способ интерпретировать значение _, как показано в этом коде, — это «переменная, на которую я не хочу ссылаться позже».

person Kintalken    schedule 22.01.2019