How do I do an operation for each element of a list, in order?
Based on these two resources:
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 predicates and still always iterate in order?
Generally, you do not iterate in Prolog. Instead, you write a rule with a pair of recursive clauses, like this: dosomething([]). dosomething([H|T]) :- process(H), dosomething(T).
Unlike arrays in other programming languages where we can directly access any element of the array, prolog lists allow direct access of the first element only which is denoted as Head. Therefore we can write a prolog list as : [Head | Rest], where Rest is the rest of the list excluding the first element Head.
You can move unification into the head of the clause and simply write: second([_, Second| _], Second). The notation for lists is to write the initial elements separated by commas and then a vertical bar to separate the list tail, i.e. the list containing the rest of the elements.
It is a data structure that can be used in different cases for non-numeric programming. Lists are used to store the atoms as a collection. In the subsequent sections, we will discuss the following topics − Representation of lists in Prolog. Basic operations on prolog such as Insert, delete, update, append.
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 turnMoreover, 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With