For some tasks you can save space by using failure driven loops.
For example, suppose we want to write a predicate, read_file/1,
which will read each clause, C, from a given file and assert them
into the database as a series of facts of the form cl(C). The first
part of this predicate takes care of opening the file; calling the
subgoal, read_all_clauses, which does the reading and asserting;
and closing the file at the end.
read_file(File):-
open(File, read, Stream),
set_input(Stream),
read_all_clauses,
close(Stream).
We could write a recursive definition of read_all_clauses/0:
read_all_clauses:-
read(Clause),
((Clause = end_of_file, !) ;
(assert(cl(Clause)), read_all_clauses)).
But a failure driven version will be more efficient:
read_all_clauses:-
repeat,
read(Clause),
((Clause = end_of_file, !) ;
(assert(cl(Clause)), fail)).