6

When programming in Prolog I often write predicates whose behavior should be semi-deterministic when called with all arguments instantiated (and whose behavior should be non-deterministic otherwise).

A concrete use case for this is my predicate walk/3, which implements graph walks. Since multiple paths can exist between two vertices, the instantiation (+,+) gives multiple choicepoints after true. These are, however, quite useless. Calling code must explicitly use once/1 for performance reasons.

%! 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.

Semi-determinism can be forced by the use of once/1 in the calling context, but I want to implement semi-determinism as a property of the predicate walk/3, and not as something that has to be treated specially every time it is called.

In addition to concerns over code aesthetics, the calling context need not always know whether its call to walk/3 is semi-deterministic or not. For example:

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

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

I have come up with the following solution, which does produce the correct behavior.

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.

However, this solution has deficiencies:

  • It's not generic enough, e.g. sometimes ground should be nonvar.
  • It is not stylistic, requiring an extra predicate wrapper every time it is used.
  • It may also be slightly inefficient.

My question is: are there other ways in which often-occurring patterns of (non-)determinism, like the one described here, can be generically/efficiently/stylistically programmed in Prolog?

Wouter Beek
  • 3,307
  • 16
  • 29
  • 1
    Just to make sure I understand your question, with a simpler example: you want a predicate that will behave as `memberchk/2` when the first argument is ground and as `member/2` when the first argument is not ground? –  Aug 04 '14 at 07:27
  • 1
    Did you already checked [mavis](http://www.swi-prolog.org/pack/list?p=mavis) pack ? – CapelliC Aug 04 '14 at 08:24
  • @Boris Indeed, that is a much simpler example. I'll update my post accordingly. – Wouter Beek Aug 04 '14 at 08:29
  • Can you give at least some context to the question? In other words, a situation in which you need to use a predicate like that? I have trouble imagining what the use case is, exactly... –  Aug 04 '14 at 08:50
  • @Boris I've update the post with a use case. – Wouter Beek Aug 04 '14 at 10:02
  • @CapelliC Mavis is a nifty library indeed. However, it targets argument types, not predicate modes. – Wouter Beek Aug 04 '14 at 10:03
  • extending mavis should be feasible and efficient - sorry I have no time at all, but maybe Michael Hendricks could help ? – CapelliC Aug 04 '14 at 10:12
  • 2
    @Boris: library(xpath) it's a good candidate for an applicative context where efficiency and control make a difference – CapelliC Aug 04 '14 at 10:18
  • 1
    Neither `memberchk/2` nor `member/2` are ISO. But at least, [`member/2`](http://www.complang.tuwien.ac.at/ulrich/iso-prolog/prologue#member) is part of the Prolog prologue. – false Aug 04 '14 at 12:06
  • @false: I don't get the motivation to exclude memberchk/2 - ok, it's a single call, but could be handy to have ready in practical case, like ?- maplist(memberchk(X), L_of_X) – CapelliC Aug 04 '14 at 13:06
  • 1
    @CapelliC: memberchk/2 has no declarative meaning: `memberchk(a,Xs),Xs=[b,a].` fails but `memberchk(a,Xs),Xs=[b,a].` succeeds. – false Aug 04 '14 at 13:09
  • 1
    most (all?) Prolog code with a cut has peculiar reading. But quite useful, or even *necessary*. What's the *real* problem about `memberchk(X, L) :- member(X, L), !.` – CapelliC Aug 04 '14 at 13:22
  • @false I've removed mention of `member/2` and `memberchk/2`, since that may have been confusing. (Also, I was wrong about the ISO status of the predicates, so thanks for pointing that out!) – Wouter Beek Aug 04 '14 at 13:31
  • 1
    @false: I suppose you mean `memberchk(a,Xs),Xs=[b,a].` vs. `Xs=[b,a], memberchk(a,Xs).`? – mat Aug 04 '14 at 14:36

3 Answers3

2

You should experiment with double negation as failure. Yes a ground goal can only be true or false, so it should not leave any choice points. Lets assume we have an acyclic graph, to make matters simple:

enter image description here

If I use this code:

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).

The Prolog system will now leave choice points for closed queries:

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

In my opinion the recommended approach, to eliminate these choice points and nevertheless have a multi-moded predicate, is to use so called meta-programming in Prolog.

meta-programming is also sometimes derogeratively called non-logical programming, since it is based on non-logical predicates such as ground/1, !/0 or (+)/1. But lets call it meta-programming when declarativity is not impacted.

You could write a wrapper smart/1 as follows, doing the same as your call_ground_as_semidet/1, but with a small nuance:

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

The Prolog system will not anymore leave a choice point for closed queries:

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

The advantage of \+ \+ over once, is that the former does not only leave no choice points, but also removes the trail. It is sometimes called the garbage collection meta-predicate of Prolog.

0

Not an answer but too long for a comment. Keep in mind I am not sure I understand exactly, so I want to re-state your question first.

To take your graph example. You want to be able to ask the following questions using the same call of the same predicate.

Given a graph,

Question 1: is vertex B reachable from vertex A (somehow)? - yes or no

Question 2: which vertices are reachable from A? - enumerate by backtracking

Question 3: from which vertices is B reachable? - enumerate by backtracking

Question 4: which A and B exist for which B is reachable from A? - enumerate by backtracking

And I might be wrong here, but it seems that answering Question 1 and Question 2 might employ a different search strategy than answering Question 3?

More generally, you want to have a way of saying: if I have a yes-or-no question, succeed or fail. Otherwise, enumerate answers.

Here comes my trouble: what are you going to do with the two different types of answers? And what are the situations in which you don't know in advance which type of answer you need? (If you do know in advance, you can use once(goal), as you said yourself.)

PS: There is obviously setof/3, which will fail if there are no answers, or collect all answers. Are there situations in which you want to know some of the answers but you don't want to collect all of them? Is this an efficiency concern because of the size and number of the answers?

  • @Boris Your reformulation of my question is correct. In the specific example I gave, Q2 and Q3 happen to use the same search strategy. The two main reasons for wanting to enforce semi-determinism are indeed performance and doing away with superfluous choicepoints. The calling context can indeed use `once/1` or `setof/3`, but I am inquiring into whether there are solutions that can be applied to the goal itself, not to (all) its calling contexts? – Wouter Beek Aug 04 '14 at 12:59
  • enumeration make easy program **lazy** Prolog. I like a lot - most pleasant Prolog code is based on proper expression of laziness. Being able to be efficient, is important. – CapelliC Aug 04 '14 at 13:00
  • @WouterBeek I get it maybe. Excuse the circular reasoning, but if the calling context does not know which question is being asked, how do you know what to do with the answer? In other words, at some point up the chain of callers you must know if it's a semi-deterministic or non-deterministic call, right? (not really sure....) –  Aug 04 '14 at 13:18
  • 1
    @Boris As an example of a calling context that does not know in advance which question is being asked, I can define a *cycle* as a closed walk: `cycle(G, V):- walk(G, V, V)`. Cycle has modes `(+,+) semidet` and `(+,-) nondet`. – Wouter Beek Aug 04 '14 at 13:22
0

Not an answer but an advice. Maybe I missunderstood your question. I think you are trying to address performance issues by forcing a predicate to be non-deterministic. That question is pointless: if p(X) is non-deterministic (multiple solutions), then p(X),! is deterministic (first solution only).

You should not address performance issues by altering program logic or predicate reversibility. I suggest a different approach:

First, take advantage of prolog indexing. For example:

cycle(+Graph:ugraph, +Vertex)

is NOT the same (in terms of performance) as:

cycle(+Vertex, +Graph:ugraph)

You should find documentation on prolog indexing (and performance impact) on the Web.

Second, write multiple implementations for the same problem. Each one will optimize performance for a different case. Then, write a predicate that chooses the best implementation for each case.

Zebollo
  • 1
  • 1