Search code examples
prologfailure-slicesuccessor-arithmetics

Finding whether a number is a multiple of another


Looking at the code below:

multiple(X,0).
multiple(X,Y) :- lt(0,X), lt(0,Y), diff(Y,X,D), multiple(X,D).

There happens to be something wrong. For your reference:
lt/2 is whether the first argument is less than the second.
diff/3 is whether the third argument is equal to the first argument minus the second.
lt/2 and diff/3 are defined correctly.

Is there a logical mistake in the definition? Is assuming that 0 is the multiple of every number problematic or is the logical mistake somewhere else? I get correct answers but the query goes to infinite loop I think.

EDIT:
here are the other definitions.

natNum(0).
natNum(s(X)) :- natNum(X).

lt(0,s(X)) :- natNum(X).
lt(s(X),s(Y)) :- lt(X,Y).

sum(0,X,X).
sum(s(X),Y,s(Z)) :- sum(X,Y,Z).

diff(X,Y,Z) :- sum(Z,Y,X).

?- multiple(X, s(s(s(s(s(s(0))))))).

where s(0) is 1, s(s(0)) is 2 etc. It gives all the desired answers for X but after the last answer, it gets stuck. I assume in an infinite recursive loop?


Solution

  • What is happening in your program? Does it loop forever, or does it only take some time since you haven't updated your hardware in recent decades? We cannot tell. (Actually, we could tell by looking at your program, but that is much too complex for the moment).

    What we can do with ease is narrow down the source of this costly effort. And this, without a deep understanding of your program. Let's start with the query:

    ?- multiple(X, s(s(s(s(s(s(0))))))).
       X = s(0)
    ;  X = s(s(0))
    ;  X = s(s(s(0)))
    ;  X = s(s(s(s(s(s(0))))))
    ;  loops. % or takes too long
    

    Isn't there an easier way to do this? All this semicolon typing. Instead, simply add false to your query. In this manner the solutions found are no longer shown and we can concentrate on this annoying looping. And, if we're at it, you can also add false goals into your program! By such goals the number of inferences might be reduced (or stays the same). And if the resulting fragment (called a ) is looping, then this is a reason why your original program loops:

    multiple(_X,0) :- false.
    multiple(X,Y) :- lt(0,X), false, lt(0,Y), diff(Y,X,D), multiple(X,D).
    
    natNum(0) :- false.
    natNum(s(X)) :- natNum(X), false.
    
    lt(0,s(X)) :- natNum(X), false.
    lt(s(X),s(Y)) :- false, lt(X,Y).
    
    ?- multiple(X, s(s(s(s(s(s(0))))))), false.
       loops.
    

    Do your recognize your program? Only those parts remained that are needed for a loop. And, actually in this case, we have an infinite loop.

    To fix this, we need to modify something in the remaining, visible part. I'd go for lt/2 whose first clause can be generalized to lt(0, s(_)).

    But wait! Why is it OK to generalize away the requirement that we have a natural number? Look at the fact multiple(X,0). which you have written. You have not demanded that X is a natural number either. This kind of over-generalizations often appears in Prolog programs. They improve termination properties at a relatively low price: Sometimes they are too general but all terms that additionally fit into the generalization are not natural numbers. They are terms like any or [a,b,c], so if they appear somewhere you know that they do not belong to the solutions.


    So the idea was to put false goals into your program such that the resulting program (failure-slice) still loops. In the worst case you put false at a wrong place and the program terminates. By trial-and-error you get a minimal failure-slice. All those things that are now stroked through are irrelevant! In particular diff/3. So no need to understand it (for the moment). It suffices to look at the remaining program.