I recall doing a free online course on computer science given by Jeffrey Ullman where he discussed how when you write any kind of aggregator, you need to give a value to the base case.
For instance, if you take the sum of an arbitrary long list of numbers, the base number is 0, and in most programing languages, including SWI-Prolog’s sum_list/2 with an empty list yields 0.
?- sum_list([], S).
S = 0.
Prolog has the concept of semidet, and one could argue the sum of an empty list should be false, not 0. Since one needs helper predicates anyway, it’s possibly easier to adapt the basic pattern to do that in Prolog than other languages by adding a L \== []
guard:
mysum(L, S) :-
L \== [],
mysum_(L, 0, S).
mysum_([], S, S).
mysum_([H|T], S1, Acc) :-
S2 is S1 + H,
mysum_(T, S2, Acc).
Reverse can be written using the same pattern as mysum (the builtin reverse/2 is more complex to make it bidirectional, whereas this simple example only works if the first argument is an input list and the second a variable to be returned).
myreverse(L, R) :-
L \== [],
myreverse_(L, [], R).
myreverse_([], R, R).
myreverse_([H|T], R, Acc) :-
myreverse_(T, [H|R], Acc).
Back to Ullman’s course, he said when writing aggregators, it’s important to think what the base or starting value is. For instance, with and you would start with true, and toggle to false as soon as you encountered a false. With or, you would start with false, and toggle to true as soon as a true appeared in the list. So and_agg([])
would be true and or_agg([])
would be false.
Long story short, there are conventions for aggregates to return a base value for an empty list, and making that the empty list when shunting or reversing is fairly established and standard I think.