Search code examples
prolognegation-as-failure

How does negation-as-failure works in Prolog?


I want to know how Prolog solves this program:

test(X, Y).
test(X, X):-!, fail.

I googled "negation as failure" but I am confused!


Solution

  • Consider the following example:

    father(nick, john).
    

    We use the predicate father(X,Y) to denote that the father of X is Y. Let's query the database:

    ?- father(nick,X).
    X = john.
    
    ?- father(john,Y).
    false.
    

    In both cases we asked who is the father of someone (nick, john respectively). In the first case, prolog knew the answer (john) however in the second it didn't and so the answer was false, meaning that john does not have any father. We might expect that, as we gave prolog no information about john's father, it would respond with unknown. That would be an open-world where if something is not known we don't assume that it's false. On the contrary, in the closed world of prolog, if we don't know something, we assume that it's false.

    Note that a world where we say that we don't know who the father of john is, based on knowing that anyone must have a father is not an open world; it can be easily modelled in prolog:

    data_father(nick, john).
    father(X,Y):-
        data_father(X,Y) -> true ; true.
    

    On the other hand, in an open world prolog you would write facts and counter facts:

    father(nick, john).
    not father(adam, X).
    

    And this is negation as failure. However, this is not what happens in your program:

    test(X, Y).
    test(X, X):-!, fail.
    

    The first clause will always succeed, regardless of the value of the arguments. In fact, exactly because of that, there is no point in naming the arguments and prolog will give you a singleton warning; you can write the clause as test(_, _).

    On the other hand, the second clause will always fail. It can fail in two ways: (1) the arguments may be different (2) the arguments are unifiable so prolog moves to the body and then fails.

    Precisely because prolog is using a closed world model there is no point of having clauses (without side-effects (but that's considered bad practise anyway)) that always fail. On the contrary, these extra calls cause your program to run slower and use more memory.

    It is also worth noting that the cut (!/0) does nothing here since when you reach it there are no more choice points. Consider however this example:

    test(X, Y).
    test(X, X):-!, fail.
    test(X, 42).
    
    ?- test(1,42).
    true ;
    true.
    
    ?- test(42,42).
    true ;
    false.
    

    In both cases prolog will create 3 choice points, one for each clause. In the first case, Prolog will successfully match the head of the first clause and succeed since there is no body. Then, it will fail matching the head of the second clause and the body will not be "executed". Finally, it will match the head of the third clause and succeed since there is no body.

    However, on the second case: Prolog will succeed in matching the head of the first clause and succeed since there is no body. Then, it will succeed in matching the head of the second clause; the cut will remove all other choice points and then it will fail due to fail. Therefore, prolog will not try the third clause.

    A few words about negation as failure since you mentioned it. Negation as failure is based on the closed world assumption; since we assume that anything that cannot be deduced from the facts we already have is wrong, if we fail to prove something it means that the opposite of it is considered true. For example, consider this:

    father(nick, john).
    fatherless(X) :- \+ father(X, _).
    

    And

    ?- fatherless(nick).
    false.
    
    ?- fatherless(john).
    true.
    

    On the contrary, in an open world prolog with the following code:

    father(nick, john).
    not father(adam, X).
    fatherless(X) :- \+ father(X, _).
    

    fatherless/1 would succeed only for adam, fail for nick and return unknown for anything else