4

I'm learning prolog using SWI Prolog and the tutorial here. I find that if I express the collatz conjecture exactly like they do in the video, it works as long as I replace #= with is which I'm guessing is a swipl vs scryer-prolog difference. But if I tweak the definition at all it seems to break, either with an error or incorrect conclusions. Why do my alternative definitions fail? Code:

use_module(library(clpfd)).

%% Does work, collatz_next(A, 1) gives A=2
collatz_next(N0, N) :-
    N0 is 2*N.
collatz_next(N0, N) :-
   N0 is 2*_ + 1,
   N is 3*N0 + 1.

%% Doesn't work, collatz_next(A, 1) gives false
%% collatz_next(N0, N) :- ((N0 mod 2) is 0),((N0 / 2) is N).
%% collatz_next(N0, N) :- ((N0 mod 2) is 1),((N0 * 3 + 1) is N).

%% Doesn't work, collatz_next(A, 1) gives false
%% collatz_next(N0, N) :- ((N0 mod 2) is 0),(N0 is 2*N).
%% collatz_next(N0, N) :- ((N0 mod 2) is 1),((N0 * 3 + 1) is N).

%% Doesn't work
%% "Arguments are not sufficiently instantiated"
%% collatz_next(N0, N) :-
%%    N0 / 2 is N.
%% collatz_next(N0, N) :-
%%    N0 is 2*_ + 1,
%%    N is 3*N0 + 1.
false
  • 10,264
  • 13
  • 101
  • 209
Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165
  • 4
    See https://www.swi-prolog.org/man/clpfd.html and its line: `:- use_module(library(clpfd)).` – brebs Feb 06 '23 at 22:21
  • 2
    `N0 / 2 is N` "is" doesn't work that way round; answer on left, arithmetic term on right, only. – TessellatingHeckler Feb 07 '23 at 02:21
  • 2
    This might be one of those questions where it is the history you don't know that you are forced to relive. In this case Markus use to publish his constraint libraries in SWI-Prolog. Now they are published using Scryer. You will have to check at Github how much/if he is keeping the SWI-Prolog versions updated. Also [Markus](https://stackoverflow.com/users/1613573/mat?tab=answers&sort=newest) does not answer questions here any more. You should post a question directly to his e-mail noted on his site. If I stated anything wrong it was in error not in intent. – Guy Coder Feb 07 '23 at 10:27
  • @TessellatingHeckler Since I don't get an error in the first two commented out implementations, I'm assuming it still "means" something valid when put in that order, even if it's not what I mean. What is the difference in meaning when swapping the order? – Joseph Garvin Feb 07 '23 at 21:32
  • @brebs I'm not sure what you're pointing out here, my code already has that line? – Joseph Garvin Feb 07 '23 at 21:33
  • @TA_intern I had to switch to `is` because `#=` didn't compile. As long as I'm using SWI Prolog is there some third syntax I'm supposed to use instead? I assume even if it's no longer updated that it had to have a working equals operator? Not sure how to find info on the difference.. when I lookup `is` it seems to be what I want?: https://www.swi-prolog.org/pldoc/man?predicate=is/2 – Joseph Garvin Feb 07 '23 at 21:36
  • 2
    @JosephGarvin You are ignoring the essential `:- ` characters at the start, to make it a command rather than a fact. – brebs Feb 07 '23 at 21:41
  • @brebs Trying it with and without it appears to make no difference – Joseph Garvin Feb 07 '23 at 22:13
  • Are you using clpfd consistently, or not? `is` is not clpfd, and has restrictions: https://www.swi-prolog.org/pldoc/man?predicate=is/2 – brebs Feb 07 '23 at 22:19
  • @JosephGarvin `is` evaluates the term on the right, not the term on the left. `4 is 8/2` is true because (eight divided by two is four) and four is four. `8/2 is 4` is false because the unevaluated term `/(8, 2)` is not four. It's a syntactically valid query, but it's never true. – TessellatingHeckler Feb 08 '23 at 03:10
  • @brebs I'm a total newb googling and trying things more or less at random, I don't even know how to tell `is` is or isn't in clpfd, I just learned I had to import that module to do anything with ints. If `is` is for comparing ints I would have assumed it had to be in clpfd since nothing with them works until I import it. – Joseph Garvin Feb 08 '23 at 19:12
  • @TessellatingHeckler shouldn't that be some sort of type error? when would you ever intentionally compare an unevaluated expression against a number? – Joseph Garvin Feb 08 '23 at 19:13
  • @JosephGarvin I don't know how to answer whether it "should" be. You'll probably need someone who has access to the ISO standard and memories from the 1970s to find out why it behaves this way and not a different way. Part of why clpfd and `#=` were developed is because `is` is very limited and does not fit well in a 'logical' and relational paradigm, only in an imperative one. – TessellatingHeckler Feb 08 '23 at 20:57

1 Answers1

5

As brebs already wrote in the comments, you have to include the line :- use_module(library(clpfd)). if you are using SWI-Prolog. However, if you are using Scryer Prolog the library is called clpz, so you have to include the line :- use_module(library(clpz)).. The predicate collatz_next/2 (from the linked video) works with both Prologs as described in the video if you use the respective libraries (I tested it with Scryer Prolog version 0.9.0. and SWI-Prolog version 8.4.2, both 64bit on a linux machine). Since it has not been mentioned in the comments yet, I would also refer you to the description of CLP(FD) and CLP(Z) in The Power of Prolog.

Now to your question about the failing alternatives. The built-in is/2 is true if the expression on the right hand side evaluates to the number on the left hand side. If the left hand side is an uninstantiated variable, the variable contains the value of the expression on the right hand side after the call of the goal. In order for this to succeed all variables in the expression on the right hand side need to be instatiated. Consider the following examples:

?- 3 is 2+1.
true.
   
?- X is 2+1.
X = 3.
   
?- 3 is X+1.
ERROR: Arguments are not sufficiently instantiated
...

?- 3 is 2+X.
ERROR: Arguments are not sufficiently instantiated
...

Furthermore if the left hand side is instantiated with a float rather than with an integer is/2 will fail:

?- 3.0 is 2+1.
false.

Now that we covered some basics let's take a look at your first predicate:

%% Does work, collatz_next(A, 1) gives A=2
collatz_next(N0, N) :-
   N0 is 2*N.
collatz_next(N0, N) :-
   N0 is 2*_ + 1,
   N is 3*N0 + 1.

Let's observe that, while your given example produces a correct answer, after pressing the ;-key it throws an instantiation error:

?- collatz_next(A, 1).
A = 2 ;
ERROR: Arguments are not sufficiently instantiated
...

What's going on here? You're posting the query collatz_next(A, 1)., hence the variable N0 in the head of your predicate is unified with the variable A and the variable N is unified with 1. With these unifications the only goal in the first rule, N0 is 2*N, now becomes A is 2*1. That yields the answer A = 2. Prolog now tries the second rule of collatz_next/2, where the first goal, N0 is 2*_ + 1 now becomes A is 2*_ + 1. Here the right hand side still contains a variable (_), hence the expression is not sufficiently instatiated to be evaluated thus Prolog throws an instantiation error.

Now let's try to use the predicate the other way around. As you can see in the Youtube-video, if N0=5 then the expected answer is N=16. However, if you query that with your predicate you get no answer and an instantiation error:

?- collatz_next(5, N).
ERROR: Arguments are not sufficiently instantiated
...

Looking at your definition of collatz_next/2 we can observe that the variable N0 in the head of the rule is unified with 5, while the second argument N remains uninstantiated. The single goal in the first rule, N0 is 2*N, becomes 5 is 2*N hence the instantiation error due to the variable on the right hand side.

Note, that the video is also showing that the most general query :- collatz_next(N0,N). is still producing answers due to the use of CLP(FD)/CLP(Z), while your version, using is/2, is again producing an instantiation error.

The next two versions of collatz_next/2 you posted (the ones with the comment %% Doesn't work, collatz_next(A, 1) gives false) fail with the first goal in the rules. Since you query :- collatz_next(A, 1). the variable N0 in the head of the rules is unified with the variable A, so the first goal in all four rules becomes ((A mod 2) is 0) and ((A mod 2) is 1) respectively. If you try these goals as queries the answer is false:

?- ((A mod 2) is 0).
false.

?- ((A mod 2) is 1).
false.

And since the first goal of the rule fails, Prolog won't even try the second goal because once you have a false in a (chain of) conjunction(s) it cannot yield true. This is the case for both rules of both predicates hence the answer to your queries is false. If you, on the other hand, try to exchange the left hand sides of is/2 with the right hand sides you will get an instantiation error:

?- (0 is (A mod 2)).
ERROR: Arguments are not sufficiently instantiated
...
   
?- (1 is (A mod 2)).
ERROR: Arguments are not sufficiently instantiated
...

Maybe think about it this way: What can you expect to happen if you try to evaluate an uninstantiated variable modulo an actual number? One reasonable expectation would be to get some sort of feedback stating that it can't be done. Another reasonable point of view would be to expect Prolog to propagate the posted goal as a constraint until it can (hopefully) be solved at a later time. This is basically what CLP(FD)/CLP(Z) does (see also the section on Constraint propagation in CLP(FD) and CLP(Z) in The Power of Prolog. Just try the above queries with (#=)/2:

?- ((A mod 2) #= 0).
A mod 2#=0.            % residual goal
   
?- ((A mod 2) #= 1).
A mod 2#=1.            % residual goal
   
?- (0 #= (A mod 2)).
A mod 2#=0.            % residual goal
   
?- (1 #= (A mod 2)).
A mod 2#=1.            % residual goal

As you can see, the posted constraints are now propagated and appear as residual goals at the end of the deduction since in these cases, with the queries just consisting of those single goals, they cannot be further resolved.

The last version you posted (marked with the comment %% Doesn't work) has the left hand side and the right hand side of is/2 the wrong way around in the single goal of the first rule, as pointed out by TessellatingHeckler in the comments. But even if you exchange them you will get an instantiation error unless the variable N0 is instantiated. But even then you will still get an instantiation error once Prolog tries the second rule because its first goal N0 is 2*_ + 1 contains a variable _ that is always uninstantiated:

?- N0 is 2*_ + 1.
ERROR: Arguments are not sufficiently instantiated
...
   
?- 1 is 2*_ + 1.
ERROR: Arguments are not sufficiently instantiated
...

The bottom line is: If you want to use low-level predicates like is/2 you have to be aware of their limitations. If you want to declaratively reason over integers you can't easily get around CLP(FD)/CLP(Z). And if you decide to use a predicate like collatz_next/2 as presented in the video, you can't exchange CLP(FD)/CLP(Z)-constraints one-to-one for low-level predicates like is/2 and expect the same results.

tas
  • 8,100
  • 3
  • 14
  • 22