Search code examples
recursionprologdeclarativeinstantiation-error

Correct use of is/2 predicate in Prolog recursion


I am a Prolog beginner with an imperative programming background. While solving assignments from this site, I bumped into two exercises: the first one regards finding the k-th elements of a list, the second one is about finding a list's length. Those are classical list-processing problems with a pretty straightforward solution, also for a beginner like me. What I was unable to understand was the (apparently) different use of the built-in is/2 predicate. As far as I am concerned, this predicate forces Prolog to carry on an arithmetic calculation and possibly assign the result to the left-hand side term. What I was told to be aware of is that, although it's possible to use variables in the right-hand side, those have to be instantiated with a variable-free term at the moment of evaluation.

For the first exercise the proposed solution is the following:

element_at(X,[X|_],1).
element_at(X,[_|L],K) :- K > 1, K1 is K - 1, element_at(X,L,K1).

and the way is is used makes sense to me: since K is provided as an argument, K1 can be immediately evaluated and supplied as a new argument in the recursive call.

By writing a solution to the second exercise, namely finding a list length, I came up with the following:

my_length([], 0).
my_length([_|L], K) :- K is K1 + 1, my_length(L, K1).

and was notified by Prolog that "arguments are not sufficiently instantiated".
The correct solution turned out to be this one:

my_length([], 0).
my_length([_|L], K) :- my_length(L, K1), K is K1 + 1.

In this case, the recursive call is made before evaluating K by means of is. In the previous exercise, it was made after evaluating K is K-1.

I think I misunderstood some key concepts. What are the cases in which is/2 has to be used before the recursive call and, on the other hand, when has it to be used after? Why is it so?
Any help and explanation is appreciated (I am using SWI-Prolog 7.6.4).


Solution

  • is should only be used when all the variables in its right-hand side term are already instantiated. You say as much yourself. So, in your at predicate, it is designed so that K must already be instantiated to a numerical expression when the predicate is called in a query -- i.e., any other query will cause an error. Since K is already instantiated, it's OK to call is.

    In the second predicate, K is the output you're expecting (as well as, possibly, an input to be validated). Since it is not guaranteed to be known when the call is made, such a query must be allowed to work. This means, K mustn't be assumed to be known. And if it's unknown, is can't be called at that time, otherwise it will cause an error.

    Thus we make the recursive call first, as it will surely instantiate K, if it weren't already.