K. Hall
K. Hall

Reputation: 169

Prolog beginner, combining rules question?

I'm trying to write a program thats based on these facts:

Boyle was born in 1627 and died in 1691. Newton was born in 1642 and died in 1727. (and so on)

I want to create a rule that determines if a person was alive during a specified year. Here is what I've come up with so far:

scientist(boyle, 1627, 1691).
scientist(newton, 1642, 1727).
alive_after(X) :- scientist(A, B, C), B < X.
alive_before(X) :- scientist(A, B, C), C > X.
alive_during(X, Year) :- alive_after(X), alive_before(X).

I believe that my first two rules are correct, but they don't seem to be working as intended when I combine them for my alive_during rule. When I run my code with this as my input:

alive_during(1628).
X = boyle

It doesn't work. What am I missing here?

Upvotes: 1

Views: 203

Answers (1)

Daniel Lyons
Daniel Lyons

Reputation: 22803

Prolog cannot unify variables that are hidden inside predicate bodies. There is no relationship between the A in alive_after/1 and the A in alive_before/1. Prolog actually told you that it didn't know what you were doing when it reported these warnings: `

|: alive_after(X) :- scientist(A, B, C), B < X.
Warning: user://2:19:
    Singleton variables: [A,C]
|: alive_before(X) :- scientist(A, B, C), C > X.

Warning: user://2:23:
    Singleton variables: [A,B]
|: alive_during(X, Year) :- alive_after(X), alive_before(X).

Warning: user://2:27:
    Singleton variables: [Year]

It is extremely important that you read these messages as if they are errors, especially when you are new to Prolog!

The solution is to make it so that Prolog is able to unify the scientists across these predicates:

alive_after(Scientist, Year)  :- scientist(Scientist, Birth, _), Year > Birth.
alive_before(Scientist, Year) :- scientist(Scientist, _, Death), Year < Death.
alive_during(Scientist, Year) :- 
    alive_before(Scientist, Year), alive_after(Scientist, Year).

You may also find that it is a little easier to follow the logic when you give your variables meaningful names. I am guilty of using extremely terse variable names when writing very general predicates, but these are actually very specific predicates and a good name can help you understand the structure of what you're doing. It would be, I think a little harder to see how this is more correct than what you wrote:

alive_after(A, X)  :- scientist(A, B, _), X > B.
alive_before(A, X) :- scientist(A, _, C), X < C.
alive_during(A, X) :- alive_before(A, X), alive_after(A, X).

With better names, it is much easier to see why your original code is incorrect, because the scientist is not actually shared between the alive_before/2 and alive_after/2 calls.

Another hint that you were confused was that this response to a query makes no sense:

?- alive_during(1628).
X = boyle

Where did X come from? Variables get unified with values from the query, they do not arrive from inside predicate bodies.

An even more direct solution would be to use Prolog's built-in between/3 predicate:

alive_during(Scientist, Year) :-
    scientist(Scientist, Birth, Death),
    between(Birth, Death, Year).

This has the added advantage that it will actually generate solutions for you:

?- alive_during(boyle, X).
X = 1627 ;
X = 1628 ;
X = 1629 ;
X = 1630 ;
X = 1631 ;

The other solution does not have this property. There are interesting predicates you can write if you do have this generating property, such as contemporaries/2:

contemporaries(S1, S2) :-
    alive_during(S1, Y),
    alive_during(S2, Y),
    dif(S1, S2).

Which generates a lot of not interesting copies of solutions, but you can get rid of them by using setof/3:

?- setof(X-Y, contemporaries(X, Y), Contemporaries).
Contemporaries = [boyle-newton, newton-boyle].

Upvotes: 2

Related Questions