6

Having recently got into Prolog I've been using it for a few simple tasks and began to wonder about using member within forall loops like the one in the trivial example below:

forall(member(A,[1,2,3,4]), print(A)).

In the case that you do something like this is it always true that forall will process the elements within the list in the same order every time its called? Does it have to be enforced by say doing something like:

A = [1,2,3,4], sort(A, B), forall(member(C,B), print(C)).

From what little research I've initially done I'm guessing that it comes down to the behaviour of member/2 but the documentation for the function on SWI-Prolog's website is very brief. It does however mention determinism with regards member/2 which gave me an inkling I might be on the right path in saying that it would always extract the elements in the same order, though I'm far from certain.

Can anyone give me any guarantees or explanations on this one?

false
  • 10,264
  • 13
  • 101
  • 209
Jonathan Rainer
  • 144
  • 1
  • 14
  • 1
    I think you can rely on it because Prolog lists are singly-linked and any other traversal will perform worse. I certainly don't see any semantic difference from naming the list first. – Daniel Lyons Feb 03 '14 at 16:38

3 Answers3

3

Non-determinism in Prolog simply refers to a predicate having potentially more than one solution. Clearly, member/2 is such a predicate. This does not mean that you have to be worried about your computation becoming unpredictable. Prolog has a well-defined computation rule which essentially says that alternative solutions are explored in a depth-first, left-to-right manner. Thus your goal member(X,[1,2,3,4]) will generate solutions to X in the expected order 1,2,3,4.

Sorting the list [1,2,3,4] will not make any difference, as it is already sorted (according to Prolog's standard term order).

A word of caution about forall/2: some Prologs define this, but it is probably less useful than you imagine, because it is not really a "loop". You can use it in your example because you only perform a print side effect in each iteration. For most other purposes, you should familiarize yourself with recursive patterns like

print_list([]).
print_list([X|Xs]) :- print(X), print_list(Xs).
jschimpf
  • 4,904
  • 11
  • 24
  • +1 nice, that's maybe rather what OP had in the mind – false Feb 04 '14 at 22:48
  • Thanks @jschimpf, that makes more sense now. Also thanks for the advice on recursion, this was more a question about the theory of what happens when using forall, but I do know that recursion is a better idea in general. – Jonathan Rainer Feb 05 '14 at 10:23
  • That is not correct. [Nondeterministic algorithm](https://en.wikipedia.org/wiki/Nondeterministic_algorithm), see also this [question](https://stackoverflow.com/questions/26450138/prolog-how-to-tell-if-a-predicate-is-deterministic-or-not/49559717). A fact tuple is deterministic because it always returns the same output when called. Member is semi-deterministic because it gives the same output for the same input. Non-deterministic means potentially different outputs for the same inputs between runs, like with random number generation for a given range. – G_V Jul 25 '18 at 14:20
1

Strictly speaking, there is no guarantee in SWI on several levels:

1mo, that member/2 or forall/2 will perform in exactly this manner, since you can redefine them.

?- [user].
member(X,X).
|: % user://1 compiled 0.00 sec, 2 clauses
true.

?- forall(member(A,[1,2,3,4]), print(A)).
[1,2,3,4]
true.

However, member/2 is defined in the Prolog prologue which covers all the details you are interested in. As for forall(A,B) it is safer to write \+ (A, \+B) instead, since this relies on standard features only. There is no definition of forall/2 as such, so it is difficult to tell what is the "right" behavior.

2do, that SWI will be standard conforming. If you read the documentation, you will note that there is no self-declaration (as for, e.g. SICStus Prolog) for standard conformance. In fact, \+ (A, \+B) is not fully conforming, as in the following example that should silently fail, but rather prints nonconforming

?-  \+ ( C= !, \+ (C,fail;writeq(nonconforming))).
false
  • 10,264
  • 13
  • 101
  • 209
  • 1
    `forall/2` is defined as a built-in predicate in B-Prolog, BinProlog, CxProlog, GNU Prolog, JIProlog, Lean Prolog, LPA WinProlog/MacProlog, Qu-Prolog, SWI-Prolog, XSB, YAP. It was also specified in the ISO Prolog Core revision draft proposal of October 2009. It was voted for inclusion the in the standard in the WG17 meeting of 2009. It seems to have been dropped some time after. But that doesn't preclude considering it a de facto standard predicate. – Paulo Moura Feb 04 '14 at 00:48
  • @PauloMoura: With the exception of GNU Prolog, none in your list declares itself as ISO which leaves open what a definition of forall/2 might mean precisely. E.g. YAP: `forall((fail,2),true)` succeeds instead of producing an error. – false Feb 04 '14 at 03:10
  • Only the top-level on YAP. Compile the same call in the body of a predicate clause defined in a file and you get a compile time error. Edge cases like this, for most usage scenarios, are a non-issue. – Paulo Moura Feb 04 '14 at 15:30
  • @PauloMoura: So YAP does not conform to ISO+the definition in N208. Nor does it behave consistently. "Edge case" is neither a notion of ISO nor N208. – false Feb 04 '14 at 15:43
  • Thats a SWI7 bug, that your query writes nonconforming. So I guess I should upvote again. I first misunderstood your post. Interesting find. Works fine in my system, in ECLiPSe prolog, in SICStus Prolog, etc.., the all give the correct No. –  Jul 25 '18 at 07:37
1

N208 has forall/2 defined + (call(Generator), + call(Test)), so this makes it less dubious. But by virtue that the ISO core standard (+)/1 does already a call/1 and that the ISO core standard (,)/2 will be subject to body conversion one can simply define it as follows in an ISO core standard Prolog:

forall(Generator, Test) :-
   \+ (Generator, \+ Test).

SWI-Prolog has also implemented this way, and the error observed by Ulrich Neumerkel will not be seen when using forall/2:

Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.18)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.

?- \+ ( C= !, \+ (C,fail;writeq(nonconforming))).
nonconforming
true.

?- forall(C=!, (C,fail;writeq(nonconforming))).
false.

Side remark:

I don't know how useful it is for loop. It seems to me using it for loops is not the right approach, since the test might fail, and then the construct also fails. I have also seen by Striegnitz and Blackburn the following definition of a helper predicate that they call failiure driven loop.

doall(Goal) :-
   Goal, fail.
doall(_).

I find myself directly writing Goal, fail; true which also does the job:

Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.18)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.

?- member(A,[1,2,3,4]), write(A), nl, fail; true.
1
2
3
4
true.