executing operation for each list element in swi-prolog and others
How do I do an operation for each element of a list, in order?
Based on these two resources:
- http://www.swi-prolog.org/pldoc/doc/swi/library/lists.pl
- http://www.swi-prolog.org/pldoc/doc_for?object=foreach/2
I imagine I can always rely on:
foreach(member(X, [1,2]), write(X)).
Is that deterministic and can I wrap the member/2 predicate as I please in my own predicat开发者_Python百科es and still always iterate in order?
Yes, but you have to worry about your predicate failing. If it can, the remaining elements in the list will not be processed because it produces a conjunction rather than a failure-driven loop.
I would be more keen to use maplist/2
since I think it is more widely used than foreach/2
but I also hadn't seen this option before. :)
Edit: Let's discuss what I mean about failure-driven loops.
There are two primitive iteration methods in Prolog: recursion and failure-driven loops. Say I want to print out every item in a list. The recursive method is going to look like this:
print_all([]).
print_all([X|Rest]) :- write(X), nl, print_all(Rest).
So given a list like [1,2,3]
, this is going to expand like so:
print_all([1,2,3])
write(1), nl, print_all([2,3])
write(1), nl, write(2), nl, print_all([3])
write(1), nl, write(2), nl, write(3), nl, print_all([])
write(1), nl, write(2), nl, write(3), nl.
This is how member/2
is usually implemented:
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).
So you can see the recursive method is pretty simple and general.
Another simple but somewhat frowned-upon method is to simulate a failure to engage the backtracking mechanism. This is called a failure-driven loop and looks like this:
print_all(List) :- member(X, List), write(X), nl, fail.
print_all(_).
When you run this version of print_all/1
, what happens is a little more complex than simple expansion.
print_all([1,2,3])
member([1,2,3], 1)
write(1), nl
fail
retry member([1,2,3], 2)
write(2), nl
fail
retry member([1,2,3], 3)
write(3), nl
fail
retry print_all(_)
true
Verbally, the fail
forces Prolog to back up to the last choice point it made and try using the next solution. Well, write/1
and nl/0
don't produce choice points because they only have one solution, but member/2
does have multiple solutions—one for each item in the list. So Prolog takes each item out of the list and prints it. Finally when member/2
runs out of solutions, Prolog backs up to the previous choice point, which is the second body of the print_all/1
predicate, which always succeeds. So the output looks the same. I think people nowadays generally prefer not to use failure-driven loops, but I don't understand the arguments well enough to parrot them usefully.
One thing that may help you to see what's going on is use the trace
predicate and step through the expansion of both versions, and see if you can make sense of the differences. My notation above is completely made up for this answer and may not be as clear.
Looking back over what I originally wrote and your actual question:
foreach
is going to be deterministicmember
will always iterate in order, because lists are defined in such a way that you must access each item in turn
Moreover, these days at least on S.O. you will get a lot of people telling you to use maplist
and its ilk, so it's probably not just going to work, but also a good idea.
精彩评论