Logics in Prolog - prolog

How can I express next 3 sentences in Prolog?
All summers are warm. If it not summer, then it is winter. Now it is winter.

Nice question.
As #larsman (well, #FredFoo now, I think) rightly said, can be a large thema. And his answer is very good indeed.
As your question could be driven by necessity of a custom language (one of main Prolog' uses), here I propose the syntax sugar for a dummy DSL (what that's means it's totally empty now...)
:- op(500, fx, all).
:- op(500, fx, now).
:- op(600, xfx, are).
:- op(700, fx, if).
:- op(399, fx, it).
:- op(398, fx, is).
:- op(397, fx, not).
:- op(701, xfx, then).
all summers are warm.
if it is not summer then it is winter.
now it is winter.
SWI-Prolog is kind enough to make red those op that get stored, i.e. can be queried easily. These are the higher precedence words declared: i.e. are,then,now.
?- now X.
X = it is winter.

How to represent this depends on what inferences you want to make. One of the simplest ways is
warm :- summer.
winter.
The "if not summer then winter" rule does not actually allow you to make any useful inferences, so you could just as well skip it. If you were to include it, it might be something like
winter :- \+ summer.
but since negation in Prolog is negation as failure, this might not do what you think it does if you expect the semantics of vanilla propositional logic.

winter(now).
warm(X) :- summer(X).
summer(X) :- \+ winter(X).
winter(X) :- \+ summer(X).
Would be one of the ways to do this.
In action:
6 ?- summer(now).
false.
7 ?- summer(tomorrow).
ERROR: Out of local stack
8 ?- warm(now).
false.

Here a solution without using negation, instead the universe of seasons is
specified.
season(summer).
season(winter).
now(winter).
warm(S) :-
season(S),
S = summer.
Some example queries:
?- now(S).
S = winter ;
false.
?- now(S), warm(S).
false.
?- warm(S).
S = summer ;
false.

Related

How to print all females in prolog if my facts only contains male property

Lets suppose my db.pl file consists of only
male(Oliver)
male(james)
male(sam)
female(X) :- \+ male(X)
Now if I query,
?- male(X).
Then this will successfully return all the males.
But if I query,
?- female(X)
Then this will not. However, if I put a name in the female predicate, it correctly gives the output as yes/no.
What am I missing here? How do I get the list of all females? Or basically list of "not male".
I thought I'd give a proper answer as well. As mentioned in the comments, the fact male(Oliver). contains the variable Oliver such that e.g. also male(catherine_the_great) succeeds. But fixing this mistake will not solve the problem. Suppose we fix the facts for male and define female as above:
male(oliver)
male(james)
male(sam)
female(X) :-
\+ male(X).
Then we can not derive male(catherine_the_great) anymore but female(catherine_the_great) succeeds:
?- male(catherine_the_great).
false.
?- female(catherine_the_great).
true.
Unfortunately, by this definition, a hamburger is also female:
?- female(hamburger).
true.
What's even worse is, when we ask if there exists someone female (at all!), we get no as an answer:
?- female(X).
false.
The reason for this is that \+ is not classical negation(*) but what is called weak negation / negation as failure. \+ male(X) is true if there is no X such that male(X) is derivable. But as soon as male/1 succeeds with any solution (e.g. male(oliver)), the negation fails. In such cases the substitution male(X) is not proper substiution for female(X) but how are we supposed to get a list of all women? A possible solution is a list of persons of which we define male/female as subsets:
person(oliver).
person(james).
person(sam).
person(jackie).
person(selma).
male(oliver).
male(james).
male(sam).
female(X) :-
person(X),
\+ male(X).
When we query a list of all women, we now get:
?- female(X).
X = jackie ;
X = selma.
I'd rather use a separate predicate listing women though because it is less error prone when adding new persons.
In general, negation as failure is rarely safe. Two rules of thumb i have for myself:
whenever \+ p(X) appears as a goal, it is fully instantiated (a ground term). In our case, this is what person assures.
p(X) terminates for every ground term X. This assures termination of the negation as well.
(*) in classical logic, the rule of generalization female(jackie) → ∃X female(X) holds but here we can not derive ∃X female(X) despite being able to derive female(jackie).

prolog family tree locating cousins

The Cousin Explainer
Write a program that when given a person of reference and a degree the program can tell me all of the nth degree cousins that person has. I've started by writing my facts: male/1 female/1 and child/2 these are populated with the names of my family members. I then began writing rules for cousins I figured id write a different rule for every degree so only up to the third cousins. The problems I'm having are as follows.
firstCousin_of(X,Y):-child(X,Z1),child(Y,Z2),child(Z1,Z),child(Z2,Z). I have this to find a first cousin but the 2nd cousin is more complicated which makes me think there has gotta be a way to do this recursively.
I need the rule, all_cousins(Person, Degree, ListOfCousins), which after figuring out all of the nth degree cousins edits a list of all cousins to only include those specific nth degree cousins. I'm used to imperative languages and all i can think about with this is how do I even convey to the rule which people to remove from the list from one rule to another.
lastly I have a rule, all_cousinsRemoved(Person, Degree, removed(Number, Direction), ListOfCousins) that I don't even know where to start with but ill need the all_cousins rule for this one because it does the same thing but also only includes cousins in the edited lists w=that were removed nth times either up or down.
The best solutions is make a several rules that call other rules, here an example
male(dicky).
male(randy).
male(mike).
male(don).
male(elmer).
female(anne).
female(rosie).
female(esther).
female(mildred).
female(greatgramma).
male(blair).
male(god).
female(god).
parent(don,randy).
parent(don,mike).
parent(don,anne).
parent(rosie,randy).
parent(rosie,mike).
parent(rosie,anne).
parent(elmer,don).
parent(mildred,don).
parent(esther,rosie).
parent(esther,dicky).
parent(greatgramma,esther).
parent(randy,blair).
male(mel).
male(teo).
parent(melsr,mel).
parent(melsr,teo).
american(anne).
american(X) :- ancestor(X,anne).
american(X) :- ancestor(anne,X).
relation(X,Y) :- ancestor(A,X), ancestor(A,Y).
father(X,Y) :- male(X),parent(X,Y).
father(god, _) :- male(god).
mother(X,Y) :- female(X),parent(X,Y).
son(X,Y) :- male(X),parent(Y,X).
daughter(X,Y) :- female(X),parent(Y,X).
grandfather(X,Y) :- male(X),parent(X,Somebody),parent(Somebody,Y).
aunt(X,Y) :- female(X),sister(X,Mom),mother(Mom,Y).
aunt(X,Y) :- female(X),sister(X,Dad),father(Dad,Y).
sister(X,Y) :- female(X),parent(Par,X),parent(Par,Y), X \= Y.
uncle(X,Y) :- brother(X,Par),parent(Par,Y).
cousin(X,Y) :- uncle(Unc , X),father(Unc,Y).
ancestor(X,Y) :- parent(X,Y).
ancestor(X,Y) :- parent(X,Somebody),ancestor(Somebody,Y).
brother(X,Y) :- male(X),parent(Somebody,X),parent(Somebody,Y), X \= Y.
Retrived from this repository

different/2 - does a pure, determinate definition exist?

different(Xs, Ys) :-
member(X, Xs),
non_member(X, Ys).
different(Xs, Ys) :-
member(Y, Ys),
non_member(Y, Xs).
While this definition using member/2 and non_member/2 is almost1 perfect from a declarative viewpoint, it produces redundant solutions for certain queries and leaves choice points all around.
What is a definition that improves upon this (in a pure manner probably using if_/3 and (=)/3) such that exactly the same set of solutions is described by different/2 but is determinate at least for ground queries (thus does not leave any useless choice points open) and omits (if possible) any redundant answer?
1
Actually, different([a|nonlist],[]), different([],[b|nonlist]) succeeds. It could equally fail. So a solution that fails for both is fine (maybe even finer).
First try!
The following code is based on the meta-predicates tfilter/3 and tpartition/4, the monotone if-then-else control construct if_/3, the reified unary logical connective not_t/3, and the reified term equality predicate (=)/3:
different([],[_|_]).
different([X|Xs0],Ys0) :-
tpartition(=(X),Ys0,Es,Ys1),
if_(Es=[], true, (tfilter(not_t(=(X)),Xs0,Xs1),different(Xs1,Ys1))).
Sample query:
?- different([A,B],[X,Y]).
A=Y , dif(B,Y), X=Y
; A=X , B=X , dif(X,Y)
; A=X , dif(B,X), dif(B,Y), dif(X,Y)
; A=Y , B=Y , dif(X,Y)
; A=Y , dif(B,X), dif(B,Y), dif(X,Y)
; dif(A,X), dif(A,Y).
Let's observe determinism when working with ground data:
?- different([5,4],[1,2]).
true.
The above approach feels like a step in the right direction... But, as-is, I wouldn't call it perfect.
Here's another try! We utilize the monotone if-then-else control construct if_/3, in combination with the reified list membership predicate memberd_t/3, and first argument indexing to avoid the creation of useless choice-points.
different(Xs,Ys) :-
different_aux(Xs,Ys,Xs,Ys).
different_aux([],[_|_],Xs0,Ys0) :-
different_aux(Ys0,[],Ys0,Xs0). % swap Xs/Ys pair
different_aux([X|Xs],Ys,Xs0,Ys0) :-
if_(memberd_t(X,Ys0),
different_aux(Ys,Xs,Ys0,Xs0), % variant: different_aux(Xs,Ys,Xs0,Ys0)
true).
First, we run a query that we expect to fail:
?- different([1,2,3],[2,3,1]).
false.
The following queries are similar to the failing query given above; each one has a single "different" item x placed at different indices in the first [1,2,3] or the second list [2,3,1]:
?- different([4,2,3],[2,3,1]), different([1,2,3],[4,3,1]),
different([1,4,3],[2,3,1]), different([1,2,3],[2,4,1]),
different([1,2,4],[2,3,1]), different([1,2,3],[2,3,4]).
true. % all subgoals succeed deterministically
OK! Let's run another (quite general) query that I used in my
previous answer:
?- different([A,B],[X,Y]).
A=X , B=X , dif(Y,X)
; A=X , dif(B,X), dif(Y,B)
; A=Y , dif(B,X), dif(Y,X)
; dif(A,X), dif(A,Y).
Compact! A big improvement over what I presented
earlier!
(Much inspired by #repeat's last answer, the names are still too clumsy)
different(Xs, Ys) :-
if_(tnotexists_inlist_t(list_memberd_t(Ys), Xs),
true,
tnotexists_inlist_t(list_memberd_t(Xs), Ys)).
tnotexists_inlist_t(_P_2, [], false).
tnotexists_inlist_t(P_2, [E|Es], T) :-
if_(call(P_2, E),
tnotexists_inlist_t(P_2, Es, T),
T = true).
Back to the roots! This variant is very close to the code given by the OP in the question.
The following is based on if_/3 and memberd_t/3.
different(Xs,Ys) :-
if_(some_absent_t(Xs,Ys),
true,
some_absent_t(Ys,Xs,true)).
some_absent_t([] ,_ ,false).
some_absent_t([X|Xs],Ys,Truth) :-
if_(memberd_t(X,Ys), some_absent_t(Xs,Ys,Truth), Truth=true).
Here is a ground query:
?- different([4,2,3],[2,3,1]), different([1,2,3],[4,3,1]),
different([1,4,3],[2,3,1]), different([1,2,3],[2,4,1]),
different([1,2,4],[2,3,1]), different([1,2,3],[2,3,4]).
true. % all subgoals succeed deterministically
And here's the (more general) query I used in previous answers:
?- different([A,B],[X,Y]).
A=X , B=X , dif(Y,X)
; A=X , dif(B,X), dif(B,Y)
; A=Y , B=Y , dif(Y,X), dif(Y,X)
; A=Y , dif(B,X), dif(B,Y), dif(Y,X)
; dif(A,X), dif(A,Y).
Next contestant to the code beauty pageant!-)
This answer shows a refactored variation of code shown in
a previous answer. It uses reified conjunction and disjunction:
and_(P_1,Q_1) :-
and_t(P_1,Q_1,true).
or_(P_1,Q_1) :-
or_t(P_1,Q_1,true).
and_t(P_1,Q_1,Truth) :-
if_(P_1, call(Q_1,Truth), Truth=false).
or_t(P_1,Q_1,Truth) :-
if_(P_1, Truth=true, call(Q_1,Truth)).
Note the two versions for both "and" and "or"; the ones with suffix _t have an extra argument for the truth value, the ones without the suffix do not and assume that Truth=true should hold.
Based on and_t/3 and on reified term inequality predicate dif/3, we define nonmember_t/3:
nonmember_t(X,Ys,Truth) :-
list_nonmember_t(Ys,X,Truth).
list_nonmember_t([] ,_, true).
list_nonmember_t([Y|Ys],X,Truth) :-
and_t(dif(X,Y), list_nonmember_t(Ys,X), Truth).
Now, let's define some_absent_t/3, different_t/3 and different/2, like so:
some_absent_t([] ,_ ,false).
some_absent_t([X|Xs],Ys,Truth) :-
or_t(nonmember_t(X,Ys), some_absent_t(Xs,Ys), Truth).
different_t(Xs,Ys,Truth) :-
or_t(some_absent_t(Xs,Ys),
some_absent_t(Ys,Xs),
Truth).
different(Xs,Ys) :-
different_t(Xs,Ys,true).
Does it still run?
?- different([A,B],[X,Y]).
A=X , B=X , dif(Y,X)
; A=X , dif(B,X), dif(B,Y)
; A=Y , B=Y , dif(Y,X), dif(Y,X)
; A=Y , dif(B,X), dif(B,Y), dif(Y,X)
; dif(A,X), dif(A,Y). % same result as before
?- different([4,2,3],[2,3,1]), different([1,2,3],[4,3,1]),
different([1,4,3],[2,3,1]), different([1,2,3],[2,4,1]),
different([1,2,4],[2,3,1]), different([1,2,3],[2,3,4]).
true. % same result as before
Looks alright!
All in all, not a huge improvement over existing answers, but IMO somewhat more readable code, and a reified version of different/2 as an added bonus!
Let's take it to the limit---by the help of list_nonmember_t/3, exists_in_t/3, and
or_/2!
some_absent_t(Xs,Ys,Truth) :-
exists_in_t(list_nonmember_t(Ys),Xs,Truth).
different(Xs,Ys) :-
or_(some_absent_t(Xs,Ys),
some_absent_t(Ys,Xs)).
The following bold bounty (+500) was offered not too long ago:
An idiomatic answer is still missing here. For example, or_t/3 should rather be (;)/3. There is more to it.
Challenge accepted! This answer is a follow-up to this previous answer.
We use the reified logical connective (;)/3, which can be defined like this:
';'(P_1,Q_1,T) :- if_(P_1, T=true, call(Q_1,T)).
Next, we define the meta-predicate call_/1. It is useful with reified predicates used in this answer. With its name and semantics, call_/1 follows if_/3, and_/2, and or_/2!
call_(P_1) :- call(P_1,true).
Using (;)/3, call_/1, and some_absent_t/3 we implement different/2:
different(As,Bs) :- call_((some_absent_t(As,Bs) ; some_absent_t(Bs,As))).
Done! That's it.
Let's re-run the queries we used in previous answers!
?- different([5,4],[1,2]).
true.
?- different([1,2,3],[2,3,1]).
false.
?- different([4,2,3],[2,3,1]), different([1,4,3],[2,3,1]), different([1,2,4],[2,3,1]),
different([1,2,3],[4,3,1]), different([1,2,3],[2,4,1]), different([1,2,3],[2,3,4]).
true.
Same queries, same answers... Looks alright to me!
Conerning solutions that use if_, I would say an alternative approach would be to use constructive negation from the beginning. Constructive negation was researched already in the 80's, a pioneer was David Chan, and still pops up from time to time.
Assume we have a Prolog interpreter which has a constructive negation (~)/2. This constructive negation (~)/2 can be used to define a declarative if-then-else as follows, lets call this operator (~->)/2:
(A ~-> B; C) :- (A, B); (~A, C).
If the Prolog interpreter has also embedded implication (=>)/2 besides constructive negation, one could alternatively define a declarative if-then-else as follows, lets call this operator (~=>)/2:
(A ~=> B; C) :- (A => B), (~A => C).
Note the switch from disjunction (;)/2 to conjunction (,)/2. Ask the Binary Decision Diagram (BDD) people why they are logically equivalent. Procedurally there are nuances, but through the backdoor of embedded implication for certain A, the second if-then-else variant will also introduce non-determinancy.
But how would we go about and introduce for example constructive negation in a Prolog interpreter. A straight forward way would be to delegate constructive negation to a constraint solver. If the constraint solver has reified negation this can be done as follows:
~ A :- #\ A.
But there not so many constraint solvers around, that would permit a sensible use for examples such as member/2 etc.. Since often they provide reified negation only for domains such as finite integers, rationals, etc.. But for a predicate such as member/2 we would need reified negation for the Herbrand universe.
Also note that the usual approaches for constructive negation also assume that the ordinary rule implication gets another reading. This means that usually under constructive negation, we could pick the ordinary member/2 definition, and get query results such as:
?- ~ member(X, [a,b,c]).
dif(X, a),
dif(X, b),
dif(X, c).
But again the reified negation will hardly easily work with defined predicates, so that the following query will probably not work:
?- #\ member(X, [a,b,c]).
/* typically won't work */
If the above succeeds than any of the declarative if-then-else such as (~->)/2 or (~=>)/2 will have less frequent use, since ordinary predicate definitions will already deliver declarative interpretation by the Prolog interpreter. But why is constructive negation not wide spread? One reason might be the large design space of such a Prolog interpreter. I noticed that we would have the following options:
Backward Chaining: We would basically spit the vanilla interpreter solve/1 into two predicates solvep/1 and solven/1. solvep/1 is responsible for solving positive goals and solven/1 is responsible for solving negative goals. When we try this we will sooner or later see that we need more careful treatment of quantifiers and probably end up with a quantifier elimination method for the Herbrand domain.
Forward Chaining: We will also notice that in backward chaining we will run into problems for the modelling of clauses with disjunction or existential quantifier in the head. This has to do with the fact that the sequent calculus suitable for Prolog has only one formula on the right hand side. So either we go multi formula on the right hand side and will loose paraconsistency, or we use forward chaining.
Magic Sets: But forward chaining will polute the solution spaces in an uncontrolled way. So we might need some form of combination of forward and backward chaining. I don't mean by that only, that we should be able dynamically switch between the two during the solution process, but I mean that we nead also a means to generate sets that direct the forward chaining process.
More problems are also noted in this answer here. This doesn't mean that sooner or later a formula will be found, to fit all this problem and solution pairs together, but it might take some more time.
Bye

Are HiLog terms still useful in modern Prolog?

Are Hilog terms (i.e. compounds having as functors arbitrary terms) still regarded as a powerful feature in XSB Prolog (or any other Prolog) ?
Are there many XSB projects currently using this feature ? which of them for example ?
I ask since as far as I understand higher order programming is equally possible using the ISO built-in call/N.
Specifically, I would like to understand if XSB is using Hilog terms just for historical reasons or if Hilog terms have considerable advantages in comparison to the current ISO standard.
Within XSB, Hilog terms are very strongly connected to the module system which is unique to XSB. XSB has a functor based module system. That is, within the same scope length(X) might belong to one module, whereas length(L, N) might belong to another. As a consequence, call(length(L), N) might refer to one module and call(length(L, N)) to another:
[Patch date: 2013/02/20 06:17:59]
| ?- use_module(basics,length/2).
yes
| ?- length(Xs,2).
Xs = [_h201,_h203]
yes
| ?- call(length(Xs),2).
Xs = [_h217,_h219]
yes
| ?- use_module(inex,length/1).
yes
| ?- length(Xs,2).
Xs = [_h201,_h203]
yes
| ?- call(length(Xs),2).
++Error[XSB/Runtime/P]: [Existence (No module inex exists)] in arg 1 of predicate load
| ?- call(call(length,Xs),2).
Xs = [_h228,_h230];
It might be that in such a context there are differences between call/N and Hilog terms. I have, however, so far not found one.
Historically, Hilog terms have been introduced 1987-1989. At that point in time, call/N already existed as built-ins in NU and as library(call) in Quintus Prolog with only cursory documentation. It has been proposed 1984 by Richard O'Keefe. On the other hand, call/N was clearly unknown to the authors of Hilog, as is exemplified on p.1101 of Weidong Chen, Michael Kifer, David Scott Warren: HiLog: A First-Order
Semantics for Higher-Order Logic Programming Constructs. NACLP
1989. 1090-1114. MIT-Press.
... Generic transitive closure can also be defined in Prolog:
closure(R, X, Y) :- C =.. [R, X, Y], call(C).
closure(R, X, Y) :- C =.. [R, X, Z], call(C), closure(R, Z, Y).
However, this is obviously inelegant compared to HiLog (see Section 2.1), since this involves both constructing a term out of a list and reflecting this term into an atomic formula using "call". The point of this example is that the lack of theoretical foundations for higher-order constructs in Prolog resulted in an obscure syntax, which partially explains why Prolog programs involving such constructs are notoriously hard to understand.
Now, this can be done with call/N like so:
closure(R, X, Y) :- call(R, X, Y).
closure(R, X, Y) :- call(R, X, Z), closure(R, Z, Y).
Which is even more general than the (=..)/2-version because R is no longer restricted to being an atom. As an aside, I'd rather prefer to write:
closure(R_2, X0,X) :- call(R_2, X0,X1), closure0(R_2, X1,X).
closure0(_R_2, X,X).
closure0(R_2, X0,X) :- call(R_2, X0,X1), closure0(R_2, X1,X).
HiLog allows goals such as
foo(X(a, Y(b))).
and ISO Prolog does not. In ISO Prolog, you'd have to write
foo(T), T=..[X, a, R], R=..[Y, b].
which is less convenient and might be slower.

Using \==/2 or dif/2

If I want to make sure that two variables do not instantiate to the same term, what is the preferred way to do it?
Let's say I need to find directed edges in a graph, and a node cannot have an edge to itself:
node(a, x, y). node(b, z, x). node(c, y, y).
(the edges here are a -> c, b -> a, but not c -> c)
The following works:
edge(A, B) :- node(A, _, X), node(B, X, _), A \== B.
This works too [swi-prolog]:
edge(A, B) :- dif(A, B), node(A, _, X), node(B, X, _).
This does not work, apparently (because neither A nor B are instantiated yet?):
edge(A, B) :- A \== B, node(A, _, X), node(B, X, _).
I guess my problem with the first solution is that, with a more complex node predicate, a lot of unnecessary unifications might take place before edge fails. The dif on the other hand is in a library, which suggests that it is not meant to be used in such a simple case (although it has the exact function that I seem to be looking for).
For elegance and didactic reasons alone, dif/2 is clearly preferable here and also in the vast majority of other cases, since as you already note "a lot of unnecessary unifications might take place" otherwise, and also because dif/2 is a pure and nicely declarative predicate that can be used in all directions and at any place in the clause body without changing the meaning of the program, in contrast to (\==)/2. dif/2 is also an autoloaded predicate in SWI-Prolog, meaning that you need not import any library explicitly to use it, and dif/2 is available like any built-in predicate.
If you use dif/2 you can reason much more easily about your code. For example, in your case, you start with:
edge(A, B) :- node(A, _, X), node(B, X, _), dif(A, B).
and then, as you know that dif/2 is a completely pure predicate, you know that you can also write this as:
edge(A, B) :- dif(A, B), node(A, _, X), node(B, X, _).
Further, since you know that dif/2 always terminates, you know that this change can at most improve the termination properties of your program.
Like all constraints, dif/2 is meant to be used. I highly recommend it instead of impure predicates that are not commutative.
In case you are worried about performance, here is a small comparison, just comparing dif/2 against the non-declarative (\==)/2 in a use case where the two predicates can be used interchangeably:
?- N = 1_000_000, time((between(1,N,_),dif(a,b),false)).
% 11,000,005 inferences, 0.352 CPU in 0.353 seconds (100% CPU, 31281029 Lips)
?- N = 1_000_000, time((between(1,N,_),a\==b,false)).
%# % 3,000,001 inferences, 0.107 CPU in 0.107 seconds (99% CPU, 28167437 Lips)
So, there are sometimes performance benefits when using (\==)/2. However, there are also much more severe drawbacks when using such a low-level predicate: It is harder to understand, more error-prone, and not declarative.
I therefore recommend to simply use dif/2 to express that two terms are different.
The queries are meta-interpreted and the overhead may outweigh the differences of dif(X,Y) and X\==Y. You should compare these two predicates:
t1:-
1000=I,
time(t1(I)).
t1(I):-
dif(X,Y),
between(1,I,X),
between(1,I,Y),
false.
t2:-
1000=I,
time(t2(I)).
t2(I):-
between(1,I,X),
between(1,I,Y),
X\==Y,
false.
On B-Prolog, I got the following results:
| ?- cl(t)
Compiling::t.pl
compiled in 0 milliseconds
loading::t.out
yes
| ?- t1
CPU time 0.14 seconds.
no
| ?- t2
CPU time 0.078 seconds.
no
| ?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )).
CPU time 0.234 seconds.
no
| ?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )).
CPU time 0.218 seconds.
First of all, dif/2 and (\==)/2 mean the same when both arguments are ground, that is variable free. So if you can ensure that the arguments will be ground — or rather sufficiently instantiated such that further instantiations will not affect the outcome of (\==)/2 — then it does not make a difference.
In your example, we would need to know for sure that answers for node/3 contain always a ground first argument. In that case, the (\==)/2 program is fine. In rare cases it might be less efficient than the dif/2 version. Think of the goal edge(X, X).
In many situations, the (\==)/2 or even (\=)/2 is significantly more efficient. On the other hand, how important is efficiency when compared to correctness?
Another way of seeing this, is to consider (\==)/2 and (\=)/2 as approximations from two sides: Only if both agree, do we have a safe final outcome.
Historically, dif/2 is one of the oldest built-in predicates. It was present in the very first Prolog system which is sometimes called Prolog 0 to distinguish it from the next version which is often perceived to be the first Prolog — the Marseille Prolog — Prolog 1. Prolog 1 did no longer have dif/2 and it is in this shape that Prolog came to Edinburgh. Also,dif/2 is not part of the ISO standard (currently) since it requires some coroutining-like mechanism. And many (rather older) Prolog systems do not have such a mechanism. However, even in ISO Prolog one could do better:
iso_dif(X, Y) :-
X == Y,
!,
fail.
iso_dif(X, Y) :-
X \= Y,
!.
iso_dif(X, Y) :-
throw(error(instantiation_error,iso_dif/2)).
(Here is another, probably more efficient implementation)
Note how the problematic cases are covered by an error that stops the entire computation.
Current Prolog systems that support dif/2 right out of the box are B, SICStus, SWI, YAP. It is in a library of IF, Ciao, XSB, Scryer.
See also this answer.
To support my claim about the overheads, here is a test in various Prologs on the same machine. In SWI, there is an overhead of a factor of 10, in B, there is no overhead. As has been noted by #nfz, numbers are slightly different when compiling things. So your mileage may vary.
SWI 6.3.4-55
?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )).
% 22,999,020 inferences, 5.162 CPU in 5.192 seconds (99% CPU, 4455477 Lips)
false.
?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )).
% 2,000,001 inferences, 0.511 CPU in 0.521 seconds (98% CPU, 3912566 Lips)
false.
B 7.8
| ?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )).
CPU time 0.364 seconds.
no
| ?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )).
CPU time 0.356 seconds.
no
YAP
?- 1000=I,time(( dif(X,Y), between(1,I,X), between(1,I,Y), false )).
% 2.528 CPU in 2.566 seconds ( 98% CPU)
no
?- 1000=I,time(( between(1,I,X), between(1,I,Y), X \== Y, false )).
% 0.929 CPU in 0.963 seconds ( 96% CPU)
no

Resources