Date

As a fun exercise I implemented a predicate to calculate the minimum and maximum element of a list.

min_and_max([A], A, A).
min_and_max([H|R], N, X):-
    min_and_max(R, RN, RX),
    N is min(H, RN),
    X is max(H, RX).

Next I wrote a version with last-call optimization.

min_and_max_2([A], A, A).
min_and_max_2([H|R], N, X) :-
    min_and_max_2_helper(R, H, H, N, X).

min_and_max_2_helper([], A, B, A, B).
min_and_max_2_helper([H|R], MinSoFar, MaxSoFar, FinalMin, FinalMax):-
    MinSoFar1 is min(MinSoFar, H),
    MaxSoFar1 is max(MaxSoFar, H),
    min_and_max_2_helper(R, MinSoFar1, MaxSoFar1, FinalMin, FinalMax).

But how do we know our second version is optimized? Well I'm not the first to ask this. This handy thread includes a tip, you can print out the stack frame and check that it never changes.

prolog_current_frame(Frame),
format("~w~n", [Frame]),

With that in place we can try:

?- min_and_max([1,2,3,4,5,6,7,8], N, X).
142
158
174
190
206
222
238
N = 1,
X = 8

And for min_and_max_2:

?- min_and_max_2([1,2,3,4,5,6,7,8], N, X).
142
142
142
142
142
142
142
N = 1,
X = 8.

I haven't yet managed the inverse modality, maybe that's the next exercise.