Will using member within a forall clause in SWI-Prolog always output the elements in the same order?
Asked Answered
E

3

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?

Epiphenomenalism answered 3/2, 2014 at 16:19 Comment(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.Alta
D
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).
Davidoff answered 4/2, 2014 at 19:16 Comment(3)
+1 nice, that's maybe rather what OP had in the mindFacing
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.Epiphenomenalism
That is not correct. Nondeterministic algorithm, see also this question. 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.Reheat
F
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))).
Facing answered 3/2, 2014 at 16:48 Comment(5)
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.Versify
@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.Facing
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.Versify
@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.Facing
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.Kilby
K
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.
Kilby answered 25/7, 2018 at 7:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.