Реализация часто встречающихся шаблонов детерминизма в Prolog

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

Конкретным вариантом использования этого является мой предикат walk/3, который реализует обход графа. Поскольку между двумя вершинами может существовать несколько путей, реализация (+,+) дает несколько точек выбора после true. Однако они совершенно бесполезны. Вызывающий код должен явно использовать once/1 по соображениям производительности.

%! walk(+Graph:ugraph, +StartVertex, +EndVertex) is semidet.
%! walk(+Graph:ugraph, -StartVertex, +EndVertex) is nondet.
%! walk(+Graph:ugraph, +StartVertex, -EndVertex) is nondet.
%! walk(+Graph:ugraph, -StartVertex, -EndVertex) is nondet.

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

Помимо заботы об эстетике кода, контекст вызова не всегда должен знать, является ли его вызов walk/3 полудетерминированным или нет. Например:

%! cycle(+Graph:ugraph, +Vertex) is semidet.
%! cycle(+Graph:ugraph, -Vertex) is nondet.

cycle(Graph, Vertex):-
  walk(Graph, Vertex, Vertex).

Я придумал следующее решение, которое действительно дает правильное поведение.

walk_wrapper(Graph, Start, End):-
  call_ground_as_semidet(walk(Graph, Start, End)).

:- meta_predicate(call_ground_as_semidet(0)).
call_ground_as_semidet(Goal):-
  ground(Goal), !,
  Goal, !.
call_ground_as_semidet(Goal):-
  Goal.

Однако у этого решения есть недостатки:

  • Это недостаточно общее, например иногда ground должно быть nonvar.
  • Это не стилистический, требующий дополнительной оболочки предиката каждый раз, когда он используется.
  • Это также может быть немного неэффективным.

Мой вопрос: есть ли другие способы, которыми часто встречающиеся паттерны (нед) детерминизма, подобные описанному здесь, могут быть в целом / эффективно / стилистически запрограммированы в Прологе?


person Wouter Beek    schedule 03.08.2014    source источник
comment
Просто чтобы убедиться, что я понимаю ваш вопрос, на более простом примере: вам нужен предикат, который будет вести себя как memberchk/2, когда первый аргумент является заземленным, и как member/2, когда первый аргумент не заземлен?   -  person    schedule 04.08.2014
comment
Проверяли ли вы пакет mavis?   -  person CapelliC    schedule 04.08.2014
comment
@Boris Действительно, это гораздо более простой пример. Я обновлю свой пост соответственно.   -  person Wouter Beek    schedule 04.08.2014
comment
Можете ли вы дать хоть какой-то контекст вопроса? Другими словами, ситуация, в которой вам нужно использовать такой предикат? Мне сложно представить, каков именно вариант использования ...   -  person    schedule 04.08.2014
comment
@Boris Я обновил пост, добавив пример использования.   -  person Wouter Beek    schedule 04.08.2014
comment
@CapelliC Mavis - действительно отличная библиотека. Однако он нацелен на типы аргументов, а не на режимы предикатов.   -  person Wouter Beek    schedule 04.08.2014
comment
расширение mavis должно быть осуществимым и эффективным - извините, у меня совсем нет времени, но, может быть, Майкл Хендрикс может помочь?   -  person CapelliC    schedule 04.08.2014
comment
@Boris: library (xpath) - хороший кандидат для прикладного контекста, где эффективность и контроль имеют значение.   -  person CapelliC    schedule 04.08.2014
comment
Ни memberchk/2, ни member/2 не являются ISO. Но, по крайней мере, member/2 является частью Пролога пролог.   -  person false    schedule 04.08.2014
comment
@false: у меня нет мотивации исключать memberchk / 2 - хорошо, это единственный вызов, но может быть удобно иметь готовый в практическом случае, например? - maplist (memberchk (X), L_of_X)   -  person CapelliC    schedule 04.08.2014
comment
@CapelliC: memberchk / 2 не имеет декларативного значения: memberchk(a,Xs),Xs=[b,a]. терпит неудачу, но memberchk(a,Xs),Xs=[b,a]. успешно.   -  person false    schedule 04.08.2014
comment
у большинства (всего?) кода Пролога с вырезом своеобразное чтение. Но весьма полезно или даже необходимо. В чем настоящая проблема с memberchk(X, L) :- member(X, L), !.   -  person CapelliC    schedule 04.08.2014
comment
@false Я удалил упоминания member/2 и memberchk/2, поскольку это могло сбивать с толку. (Кроме того, я ошибался насчет ISO-статуса предикатов, поэтому спасибо, что указали на это!)   -  person Wouter Beek    schedule 04.08.2014
comment
@false: я полагаю, вы имеете в виду memberchk(a,Xs),Xs=[b,a]. vs. Xs=[b,a], memberchk(a,Xs).?   -  person mat    schedule 04.08.2014


Ответы (3)


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

введите описание изображения здесь

Если я использую этот код:

edge(a, b).         edge(a, c).
edge(a, d).         edge(b, c).
edge(c, d).         edge(c, e).
edge(d, e).

path(X,X).
path(X,Y) :- edge(X,Z), path(Z,Y).

Система Prolog теперь будет оставлять точки выбора для закрытых запросов:

?- path(a, e).
true ;
true ;
true ;
true ;
true ;
false.

На мой взгляд, рекомендуемый подход, чтобы устранить эти точки выбора и, тем не менее, иметь многомодовый предикат, заключается в использовании так называемого метапрограммирования в Prolog.

метапрограммирование также иногда дегративно называют нелогическим программированием, поскольку оно основано на нелогических предикатах, таких как земля / 1,! / 0 или (+) / 1. Но давайте назовем это метапрограммированием, если это не влияет на декларативность.

Вы можете написать оболочку smart / 1 следующим образом, сделав то же самое, что и ваш call_ground_as_semidet / 1, но с небольшим нюансом:

smart(G) :- ground(G), !, \+ \+ G.
smart(G) :- G.

Система Prolog больше не будет оставлять точку выбора для закрытых запросов:

?- smart(path(a,e)).
true.

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

person Mostowski Collapse    schedule 02.08.2019

Не ответ, но слишком длинный для комментария. Имейте в виду, что я не уверен, что правильно понимаю, поэтому сначала хочу повторить ваш вопрос.

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

Учитывая график,

Вопрос 1: достижима ли вершина B из вершины A (каким-то образом)? - да или нет

Вопрос 2: какие вершины достижимы из A? - перечислить с возвратом

Вопрос 3: из каких вершин достижима точка B? - перечислить с возвратом

Вопрос 4: какие A и B существуют, для какого B достижимо из A? - перечислить с возвратом

И я могу ошибаться здесь, но кажется, что ответ на вопрос 1 и вопрос 2 может использовать другую стратегию поиска, чем ответ на вопрос 3?

В более общем плане вы хотите иметь способ сказать: если у меня есть вопрос типа «да» или «нет», добейтесь успеха или нет. В противном случае перечислите ответы.

Вот и моя проблема: что вы собираетесь делать с двумя разными типами ответов? И в каких ситуациях вы заранее не знаете, какой тип ответа вам нужен? (Если вы знаете заранее, вы можете использовать once(goal), как вы сами сказали.)

PS: Очевидно, есть setof/3, который выйдет из строя, если нет ответов, или соберет все ответы. Есть ли ситуации, когда вы хотите знать некоторые ответы, но не хотите собирать их все? Это проблема эффективности из-за размера и количества ответов?

person Community    schedule 04.08.2014
comment
@Boris Ваша переформулировка моего вопроса верна. В конкретном примере, который я привел, Q2 и Q3 используют одну и ту же стратегию поиска. Двумя основными причинами, по которым возникает желание применить полудетерминизм, на самом деле являются производительность и устранение лишних точек выбора. Контекст вызова действительно может использовать once/1 или setof/3, но я интересуюсь, есть ли решения, которые могут быть применены к самой цели, а не ко (всем) ее контекстам вызова? - person Wouter Beek; 04.08.2014
comment
перечисление упрощает программу ленивым Прологом. Мне очень нравится - самый приятный код Пролога основан на правильном выражении лени. Уметь быть эффективным - это важно. - person CapelliC; 04.08.2014
comment
@WouterBeek Может быть, я понял. Простите за круговое рассуждение, но если вызывающий контекст не знает, какой вопрос задается, как узнать, что делать с ответом? Другими словами, в какой-то момент в цепочке вызывающих абонентов вы должны знать, полудетерминированный это вызов или недетерминированный, верно? (не уверен....) - person ; 04.08.2014
comment
@Boris В качестве примера контекста вызова, который не знает заранее, какой вопрос задается, я могу определить цикл как закрытую прогулку: cycle(G, V):- walk(G, V, V). Цикл имеет режимы (+,+) semidet и (+,-) nondet. - person Wouter Beek; 04.08.2014

Не ответ, а совет. Может я неправильно понял твой вопрос. Я думаю, вы пытаетесь решить проблемы с производительностью, заставляя предикат быть недетерминированным. Этот вопрос бессмысленен: если p(X) недетерминировано (несколько решений), то p(X),! является детерминированным (только первое решение).

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

Во-первых, воспользуйтесь индексированием пролога. Например:

cycle(+Graph:ugraph, +Vertex)

НЕ то же самое (с точки зрения производительности), как:

cycle(+Vertex, +Graph:ugraph)

Вы должны найти документацию по индексированию пролога (и влиянию на производительность) в Интернете.

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

person Zebollo    schedule 26.05.2016