Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prolog taking inverse of a predicate

Tags:

prolog

I got a database that looks like

hasChild(person1, person2).
hasChild(person1, person3).
hasChild(person4, person5).

Which means that (for example) person1 has child named person2.

I then create a predicate that identifies if the person is a parent

parent(A):- hasChild(A,_).

Which identifies if the person is a parent, i.e. has any children

Then I try to create a predicate childless(A) that should return true if the user doesn't have any children which is basically an inverse of parent(A).

So I have 2 questions here:

a) is it possible to somehow take an "inverse" of a predicate, like childless(A):-not(parent(A)). or in any other way bypass this using the hasChild or any other method?

b) parent(A) will return true multiple times if the person has multiple children. Is it possible to make it return true only once?

like image 788
DannyPhantom Avatar asked Nov 24 '15 23:11

DannyPhantom


2 Answers

For problem 1, yes. Prolog is not entirely magically delicious to some because it conflates negation and failure, but you can definitely write:

childless(X) :- \+ hasChild(X, _).

and you will see "true" for people that do not have children. You will also see "true" for vegetables, minerals, ideologies, procedures, shoeboxes, beer recipes and unfeathered bipeds. If this is a problem for you, a simple solution is to improve your data model, but complaining about Prolog is a very popular alternative. :)

For problem 2, the simplest solution is to use once:

parent(A) :- once(hasChild(A, _)).

This is a safer alternative to using the cut operator, which would look like this:

parent(A) :- hasChild(A, _), !.

This has a fairly significant cost: parent/1 will only generate a single valid solution, though it will verify other correct solutions. To wit:

?- parent(X).
X = person1.

Notice it did not suggest person4. However,

?- childless(person4).
true.

This asymmetry is certainly a "code smell" to most any intermediate Prolog programmer such as myself. It's as though Prolog has some sort of amnesia or selective hearing depending on the query. This is no way to get invited to high society events!

I would suggest that the best solution here (which handles the mineral/vegetable problem above as well) is to add some more facts about people. After all, a person exists before they have kids (or do they?) so they are not "defined" by that relationship. But continuing to play the game, you may be able to circumvent the problem using setof/3 to construct a list of all the people:

parent(Person) :- 
  setof(X, C^hasChild(X, C), People), 
  member(Person, People).

The odd expression C^hasChild(X, C) tells Prolog that C is a free variable; this ensures that we get the set of all things in the first argument of hasChild/2 bound to the list People. This is not first-order logic anymore folks! And the advantage here is that member/2 will generate for us as well as check:

?- parent(person4).
true.

?- parent(X).
X = person1 ;
X = person4.

Is this efficient? No. Is it smart? Probably not. Is it a solution to your question that also generates? Yes, it seems to be. Well, one out of three ain't bad. :)

As a final remark, some Prolog implementations treat not/1 as an alias for \+/1; if you happen to be using one of them, I recommend you not mistake compatibility with pre-ISO conventions for a jovial tolerance for variety: correct the spelling of not(X) to \+ X. :)

like image 178
Daniel Lyons Avatar answered Nov 20 '22 11:11

Daniel Lyons


Here's another way you could do it!

Define everything you know for a fact as a Prolog fact, no matter if it is positive or negative.

In your sample, we define "positives" like person/1 and "negatives" like childless/1. Of course, we also define the predicates child_of/2, male/1, female/1, spouse_husband/2, and so on.

Note that we have introduced quite a bit of redundancy into the database.

In return, we got a clearer line of knowns/unknowns without resorting to higher-order constructs.

We need to define the right data consistency constraints:

% There is no person which is neither male nor female.
:- \+ (person(X), \+ (male(X) ; female(X))).

% Nobody is male and female (at once).
:- \+ (male(X), female(X)).

% Nobody is childless and parental (at once).
:- \+ (childless(X), child_of(_,X)).

% There is no person which is neither childless nor parental.
:- \+ (person(X), \+ (childless(X) ; child_of(_,X))).

% There is no child which is not a person.
:- \+ (child_of(X,_), \+ person(X)).

% There is no parent which is not a person.
:- \+ (child_of(_,X), \+ person(X)).

% (...plus, quite likely, a lot more integrity constraints...)

This is just a rough sketch... Depending on your use-cases you could do the modeling differently, e.g. using relations like parental/1 together with suitable integrity constraints. YMMY! HTH

like image 29
repeat Avatar answered Nov 20 '22 13:11

repeat