Loop structures: Difference between revisions

→‎Pop11: Added Prolog section.
m (Fix the Dao example.)
(→‎Pop11: Added Prolog section.)
Line 504:
Array.iteri</lang>
 
=={{header|Prolog}}==
==[[Pop11]]==
 
There are three primitive methods of looping in Prolog: recursion, fail-driven loops, and repeat-driven loops.
 
<lang Prolog>% recursion as loop
print_each_element([E|T]) :- writeln(E), print_each_element(T).
print_each_element([]).
 
% fail-driven loop
fact(foo).
fact(bar).
fact(baz).
print_each_fact() :-
( fact(X), writeln(X), fail
; true ).
 
% equivalently
%print_each_fact :- fact(X), writeln(X), fail.
%print_each_fact.
 
% repeat-driven loop
print_each_fact_again :-
repeat,
fact(X),
writeln(X),
X = baz,
!.
 
go :-
print_each_element([foo, bar, baz]),
print_each_fact,
print_each_fact_again.</lang>
 
Of the three recursion is the favoured approach as it requires no non-logical predicates and is thus easy to read in its declarative form.
 
The fail-driven loop form is a(n ab)use of the built-in backtracking mechanism of Prolog's reasoning engine. In the specific example provided, fact(X) will first succeed, binding "foo" to X. It will then write "foo" to the output (as a side effect of the writeln/1 predicate). It then hits the call to fail/0 which is a non-logical predicate which always fails and thus always triggers backtracking. On backtracking, the runtime will try fact(X) again and will find that it is true when X is bound to "bar". This will then print and backtrack again. A third time binds to and prints "baz". A fourth time will fail because there is no more solution to the goal "fact(X)". This triggers a further backtrack and a try on the second branch of the disjunction. That second branch invokes the true/0 predicate which always succeeds. This exits the query with an overall success.
 
The repeat-driven loop uses similar (ab)use of the backtracking mechanism. Instead of employing a predicate that always fails, however, it employs one that will always succeed: repeat/0. Thus, in this sample, fact(X) works as before, as does writeln(X), but the attempt to unify X with "baz" will fail for the first two attempts, causing the system to backtrack until it hits repeat. Since repeat always succeeds it drives the engine forward again, testing each fact in succession. Once X is unified with "baz" (which is to say once X contains the value "baz") the predicate carries on. The cut operator !/0, guarantees that the predicate won't be re-entered later.
 
As with any language permitting higher-order invocations, using the looping primitives directly as above is often not a desirable thing. Instead higher-order features would be used.
 
<lang Prolog>% using maplist/2 to replace explicit recursion on a list
print_each_element(L) :- maplist(writeln, L).
 
% using forall/2 to replace an explicit fail-driven loop
fact(foo).
fact(bar).
fact(baz).
print_each_fact() :- forall(fact(X), writeln(X)).</lang>
 
There are a myriad of such predicates available in a useful Prolog implementation (SWI-Prolog provides, non-exhaustively: include/3, exclude/3, partition/4-5, maplist/2-5, foldl/4-7, scanl/4-6, aggregate/3-4, aggregate_all/3-4, forall/2, findall/3-4, findnsols/4-5, bagof/3, setof/3, … just as the more fundamental wrappings.) If the provided predicates do not permit the kinds of functionality desired for common patterns, it is trivial to make a new one. As an illustration, this is the source code for forall/2:
 
<lang Prolog>:- meta_predicate forall(0,0).
forall(A, B) :- \+ (call(A), \+ call(B)).</lang>
 
==[[{{header|Pop11]]}}==
 
=== until ===