Very interesting! Let's consider for example the definition of sum_list/2 which is shown in Fig. 1, Fig. 2 and Fig. 3:

%% sum_list(+Number_List, ?Result)
% Unifies Result with the sum of the numbers in Number_List;
% calls error/1 if Number_List is not a list of numbers.
sum_list(Number_List, Result) :-
sum_list(Number_List, 0, Result).

% sum_list(+Number_List, +Accumulator, ?Result)
sum_list([], A, A). % At end: unify with accumulator.
sum_list([H|T], A, R) :- % Accumulate first and recur.
number(H),
!,
B is A + H,
sum_list(Rest, B, R).
sum_list(_, _A, _R) :- % Catch ill-formed arguments.
error('first arg to sum_list/2 not a list of numbers').

Compiling it with Scryer Prolog, I get a warning:

$ scryer_prolog sum_list.pl
Warning: singleton variables T, Rest at line 4 of sum_list.pl
true.

A singleton variable often indicates a mistake in the code. And indeed, the sample code uses Rest where it apparently meant to use T (or vice versa). So, I change the second clause of sum_list/3 to:

sum_list([H|T], A, R) :-
number(H),
!,
B is A + H,
sum_list(T, B, R).

And now we're ready to use the predicate! Let's ask Prolog the most general query: Which answers are there in general? So, a query where all arguments are logic variables:

?- sum_list(Ls, R).
Ls = [], R = 0
; error(existence_error(procedure,error/1),error/1).

The existence error is due to the use of the non-standard predicate error/1 in the code sample. The predicate apparently meant to throw an exception telling us:

first arg to sum_list/2 not a list of numbers

But the query did not restrict the first argument at all, so it may just as well be a list of numbers! The predicate probably meant to say that the argument is not sufficiently instantiated. In that case, it should have thrown an instantiation error. The standard predicate (is)/2 throws such an instantiation error for us in such cases. Also, it throws type errors for us! A type error is categorically different from an instantiation error: From a logical perspective, a type error can be replaced by silent failure, but an instantiation error can not.

We can therefore write the second clause as:

sum_list([H|T], A, R) :-
B is A + H,
sum_list(T, B, R).

and also remove the third clause entirely. We now get:

?- sum_list(Ls, R).
Ls = [], R = 0
; error(instantiation_error,(is)/2).

From a logical perspective, that's OK: The predicate tells us that too little is known to make any statement, and a more specific query may yield solutions.

Also, this version correctly distinguishes between type and instantiation errors, and we now get for example:

?- sum_list("abc", R).
error(type_error(evaluable,a),(is)/2).

As I see it, a key attraction of logic programming is that we are able to reason logically about our code. This holds as long as certain logical properties are preserved. The paper hints at such properties for example with the concept of steadfastness, which it defines in Section 5.1: A predicate "must work correctly if its output variable already happens to be instantiated to the output value". How can we tell though which variables are output variables, and also why even distinguish a particular variable as "output variable"? Should this not hold for all variables?

A particularly important logical property is called monotonicity: Generalizing a query (or program) can at most add solutions, never remove them. With monotonic predicates, debugging is very nice: For instance, if a predicate unexpectedly fails, then we can generalize it by removing goals, and if the remaining fragment still fails unexpectedly, then there must be a mistake in that fragment. Scryer Prolog provides library(debug) for this approach of declarative debugging:

https://www.scryer.pl/debug

In Scryer Prolog, we can define the specific case of arithmetic over integers with constraints over integers, for example like this:

:- use_module(library(clpz)).
:- use_module(library(lists)).
:- use_module(library(lambda)).

list_sum(Is, S) :- foldl(\I^S0^S^(S #= S0 + I), Is, 0, S).

Higher-order predicates such as maplist/N and foldl/N retain logical properties of the predicates that occur as arguments.

The most general query now works as expected:

?- list_sum(Is, Sum).
Is = [], Sum = 0
; Is = [Sum], clpz:(Sum in inf..sup)
; Is = [_A,_B], clpz:(_A+_B#=Sum)
; Is = [_A,_B,_D], clpz:(_A+_B#=_C), clpz:(_C+_D#=Sum)
; ... .

The predicate does not terminate, as expected, because we expect solutions for lists of all lengths:

?- list_sum(Is, Sum), false.
loops.

And other cases are simply specific instances of the most general query:

?- list_sum([1,2,3], Sum).
Sum = 6.

Note that I have changed the predicate name from sum_list/2 to list_sum/2, because the list is the first argument, and the sum is the second argument. So, I am now using "sum" no longer as a verb, but as a noun, because that seems more appropriate for code that is declarative, not imperative: We describe what is true, not what must be done, and our code works in all directions and also with different execution strategies. integers_sum/2 may be an even better name in this case.

One other naming convention I like to use is to append an "s" for logic variables that stand for lists, such as "Is" for a list of integers.