Search code examples
prolog

Month not associated with unique value?


I'm just learning prolog and stumbled across a problem. I'm not quite sure if the problem is syntactic or if what I'm trying to do doesn't make any sense at all. Maybe I don't yet fully understand what prolog can and can't do.

here is my code:

month(jan).
month(feb).
month(mar).
month(apr).

number(X,Y) :-
  month(X),
  ( Y==1 ; Y==2 ; Y==3 ; Y==4 ),
  not(M is X),
  not(N is Y),
  not(number(M,N)).

number(jan, 1).
number(feb, 2).
number(mar, 3).

So I have four different months. And now I want to give everyone a unique identifier (in this case a number). The number(X,Y) function should be defied in that way that not two moth can have the same identifier.

If I now ask:

number(jan,X).
number(feb,Y).
number(mar,Z).

it get the correct answer:

X = 1.
Y = 2.
Z = 3. 

but if I now ask

number(apr, X)

the program just answers with false.

I thought Prolog should be able to conclude that X has to be 4 because it is the only possible option.


Solution

  • These queries:

    number(jan,X).
    number(feb,Y).
    number(mar,Z).
    

    Succeed by unifying with these facts:

    number(jan, 1).
    number(feb, 2).
    number(mar, 3).
    

    But the query:

    number(apr, X).
    

    Is failing on this rule:

    number(X,Y) :-
      month(X),
      ( Y==1 ; Y==2 ; Y==3 ; Y==4 ),
      not(M is X),
      not(N is Y),
      not(number(M,N)).
    

    If we trace the goals, you'll find month(apr) succeeds. The next disjunctive goals are trying to compare if Y is equal to (==/2) an integer. As Y is a variable, they are not equal and so the rule fails here. Instead, you're trying to unify (=/2) Y with the integers.

    But that's not going to be enough to fix it. not/1 doesn't exist in all Prolog dialects. It's use is discouraged so as not to cause confusion with the meaning of "not" in logic, which has no procedural meaning. Instead you should use the \+ operator.

    But that's still not going to be enough to fix it, the rule will next encounter \+ (M is N). is/2 is for evaluating a mathematical expression and then unifying the variable with the result. You've not got any mathematical expression here. So you'd need to replace is/2 with =/2, the unification operator: \+ (M = X). This goal will always fail due to how \+ is evaluated, seen as M is a variable, it will always unify with X, so (M = X) is always true when one of the terms is a variable. If this is true, then \+ true is false.

    So the re-written rule (still broken) becomes:

    number(X, Y) :-
        month(X),
        ( Y = 1; Y = 2; Y = 3; Y = 4 ),
        \+ number(X, Y).
    

    This is still not enough to fix it because it's now infinitely recursive, it's referencing itself as a goal when you're needing it to reference the number/2 facts. It's best to have distinct names for facts and rules. You've already got some month facts, so why not make use of them and re-write as:

    month(jan, 1).
    month(feb, 2).
    month(mar, 3).
    month(apr).
    
    number(X, Y) :-
        month(X, Y).
    number(X, Y) :-
        month(X),
        ( Y = 1 ; Y = 2 ; Y = 3 ; Y = 4 ),
        \+ month(_, Y).
    

    Now you can query:

    ?- number(X, Y).
    X = jan,
    Y = 1 ;
    X = feb,
    Y = 2 ;
    X = mar,
    Y = 3 ;
    X = apr,
    Y = 4.