Relation as predicate name or predicate argument?

Hi!

I note that i am switching a lot between describing relations with a predicate name, e.g.

likes(mary, john).

and describing relations with a predicate argument, e.g.

x(mary, likes, john).

Beyond differences in readability, are there any other advantages/disadvantages with one or the other form? E.g. in terms of performance?

/JCR

1 Like

I personally prefer to use the 2nd approach as it is more flexible.

For example,

Suppose we have the following relations:

likes(mary, john).
likes(alice, david).
hates(mary, peter).
hates(peter, john).

I’d like to know what’s the relation between mary and peter?

How to accomplish this by the 1st approach?

By the 2nd approach, I just write:

x(mary, likes, john).
x(alice, likes, david).
x(mary, hates, peter).
x(peter, hates, john).

?- x(mary, R, peter).
R = hates.
2 Likes

After an interesting discussion with @hakank i tried the “relation as argument” - concept on this problem. Now my brain hurts. Apparently I am Theresa as well as Theresa’s daughter :upside_down_face:

% THERESA

% "If Teresa's daughter is my daughter's mother, then what is my relationship to Teresa?
% Choose the correct one from below and write down in the comment section.
% a) Am I her mother
% b) Am I her grandmother
% c) Am I her granddaughter
% d) Am I her daughter
% e) I am Teresa"

relation(motherhood, theresa, theresasDoughter). % If theresas doughter
relation(daughterhood, myDoughter, me). % is my doughter's 
relation(motherhood, theresasDoughter, myDoughter). % mother
forwardBackward(motherhood, daughterhood).

same(X1 = X2, Y1 = Y2):-
    forwardBackward(R1, R2),
    relation(R1, X1, Y1),
    relation(R2, Y2, X2).

go:- same(P1, P2), writeln(same(P1, P2)).

OUTPUT:

same(theresa=me,theresasDoughter=myDoughter)
same(theresasDoughter=me,myDoughter=myDoughter)
1 Like

I think this also is kind of cool.

transitive(causes).
transitive(contains).
transitive(equals).

x(a, causes, b).
x(b, causes, c).
x(a, contains, b).
x(b, contains, c).
x(a, equals, b).
x(b, equals, c).

relation(A, R, B):- transitive(R), x(A, R, B).
relation(A, R, B):- transitive(R), x(A, R, Z), relation(Z, R, B).

Another example where the relation as argument pattern is useful is in “relation composition”, e.g.

x(moisture, causes, mold).
x(mold, causes, allergy).

x(sanding, causes, dust).
x(dust, prevents, adhesion).

x(vaccine, prevents, disease).
x(disease, prevents, health).

x(food, prevents, hunger).
x(hunger, prevents, energy).
x(energy, causes, happiness).

relation(X, R, Y):- x(X, R, Y).
relation(X, causes, Y):- x(X, R, Z), relation(Z, R, Y).
relation(X, prevents, Y):- x(X, causes, Z), relation(Z, prevents, Y).
relation(X, prevents, Y):- x(X, prevents, Z), relation(Z, causes, Y).

go:- relation(X, R, Y), writeln(relation(X, R, Y)), fail.

OUTPUT:

relation(moisture,causes,mold)
relation(mold,causes,allergy)
relation(sanding,causes,dust)
relation(dust,prevents,adhesion)
relation(vaccine,prevents,disease)
relation(disease,prevents,health)
relation(food,prevents,hunger)
relation(hunger,prevents,energy)
relation(energy,causes,happiness)
relation(moisture,causes,allergy)
relation(vaccine,causes,health)
relation(food,causes,energy)
relation(food,causes,happiness)
relation(sanding,prevents,adhesion)
relation(hunger,prevents,happiness)

Looks like Lisp…

You might consider putting the name of the relation in the first position, so that it doesn’t get awkward with unary or ternary relations. It may also increase efficiency because of first-argument indexing.

1 Like

You’re assuming that e.g. X1 and X2 are somehow the same, when the logic isn’t enforcing that. This would make more sense:

relation(R1, X1, Y1),
relation(R2, Y1, X1).

That depends. If you always know the relation name when writing the query, using binary predicates R(X,Y) is more efficient as the system only has consider a maximum of two arguments to create its indexes. In addition, Prolog will complain if a relation does not exist. If you do not (always) know the relation, r(X,R,Y) wins easily in terms of conciseness of the program and is quite likely to win in terms of performance. Your millage may vary depending on distribution of the values though. Note that r(X,R,Y) is the data model of RDF :slight_smile:

1 Like

Thanks for your suggestion :slight_smile: The idea behind same/2 was to get Prolog to figure out that two things can be the same even if the constants that represent them are different. This is perhaps a better example:

relation(to, stockholm, london).
relation(from, capitalOfEngland, capitalOfSweden).
forwardBackward(to, from).

same(X1 = X2, Y1 = Y2):-
    forwardBackward(R1, R2),
    relation(R1, X1, Y1),
    relation(R2, Y2, X2).

go:- same(P1, P2), writeln(same(P1, P2)).

OUTPUT:
same(stockholm=capitalOfSweden,london=capitalOfEngland)

Thanks for the suggestion!

Thanks! Good to know :slight_smile:

How about:

relation(mother, teresa, teresas_daughter).
relation(mother, teresas_daughter, my_daughter).
relation(mother, me, my_daughter).

relation_calc(Rel, M, D) :-
    relation(Rel, M, D).

relation_calc(grandmother, GM, D) :-
    relation(mother, GM, M),
    relation(mother, M, D).

relation_calc(mother, GM, M) :-
    relation_calc(grandmother, GM, D),
    relation_calc(mother, M, D).

relation_calc(same_person, M1, M2) :-
    dif(M1, M2),
    relation_calc(mother, M1, D),
    relation_calc(mother, M2, D),
    % Break symmetry
    M1 @> M2.

This gives the seemingly-sensible answer:

?- relation_calc(same_person, P1, P2).
P1 = teresas_daughter,
P2 = me
1 Like

Nice solution :slight_smile:

Representing a relation as an argument to another relation is cheating! Prolog is supposed to be a first order language, where relations are between objects (constants, or functions, which Prolog doesn’t clearly define anyway). The language of relations over relations is second-order logic!

But Prolog lets you define relations over relations, because it doesn’t make a clear distinction between a predicate symbol (i.e. the name of a relation) and a constant (i.e. the name of an object in the domain of discourse). What’s more, Prolog variables are allowed to unify with any Prolog “term”, and Prolog terms are a broad enough category to include lists of definite clauses. A list of definite clauses is a logic program, so Prolog variables can unify with entire Prolog programs.

For example, this is perfectly legit Prolog:

modify(Program, Modified):- 
 ... 

Where Program is a variable that unifies to a list of definite clauses.

What is the logical order of a language where logic programs can be the arguments of logic programs? I don’t know, but it sure is not the first order! The inevitable conclusion is that Prolog is not a first-order language, in the strict sense of First Order Logic. It’s some kind of higher-order language. If it wasn’t, we wouldn’t be able to process Prolog programs as data, or to write meta-interpreters and do all those other wonderful and mind-bending things that we all love Prolog for (and everyone else finds so confusing).

What does that have to do with your question, Jean-Christophe? Well, for me it’s helpful to think, when I’m creating a language to express my problem domain, whether a relation is a relation between objects (people, animals, numbers…) or a relation betwen relations (“father”, “mother”, “friends”…). If I want to represent a relation between objects, like “father”, I represent it as a first-order relation: father(jack, mary). If I want to represent a relation over relations, I represent it as a second-order relation: family_relation(father). Then I can write code to combine the two if I want.

So it’s OK to cheat, if you know what you’re cheating for. Wow, that sounds completely wrong, doesn’t it? :confused:

3 Likes

Thats really insightful, I appreciate your expertise! For me this comment is really relevant because it reminds me to be careful when writing papers how i label Prolog. Some time ago i thought that it is a subset of FOL because the basic building block is a definite clause. But, as you say, being able to unify relations (and even whole programs) with variables takes it well beyond FOL. And that’s also what makes it so interesting. So i don’t think it’s cheating, more a matter of labelling Prolog correctly :wink:

I like you advice to think carefully about what i want to represent (relations between objects or relations between relations). Going to follow it :slight_smile:

1 Like

The suggestion is a nice representation for other/foreign languages. IIRC someone in this forum told me to represent R functions like sin(x) not by Prolog sin(x), but cleaner e.g. rfunc(sin, [x]). Basically because there’s no 1-1 mapping between the syntax elements of the two languages.

I did not follow this advice (and, as could probably have been expected, I lost a lot of time because of that).

I’m not so sure about that. sin(x) looks a lot nicer and is only a problem if in the Prolog context it may be ambiguous whether this represents an R function or a Prolog data term. If you go for rfunc(…), you still can go for rfunc(sin(x)). I don’t see a good reason for representing the arguments as a list. I’ve seen people splitting the name and arguments before in similar contexts. I still don’t see the advantage. The two representations are straightforward linked by =../2 or compound_name_arguments/3.

As (AFAIK) compounds have no meaningful mapping to R (with the exception of c(...) to represent an R vector), compounds seem to be an adequate representation for R functions. That is what has been used by real (and rserve-client) and I have not seen any problems with that.

At some point, representing R expressions in Prolog gets hard. real tries to stretch this as far as possible. rserve-client is more conservative and adds quasi quotations to deal with the rest.

I am trying to remember what the problem actually was. I think it was the usual struggle with clean and defaulty representations; things such as sin(x) versus rfunc(sin, [atom(x)]). The former required a lot of case distinctions and cuts and was very cumbersome to debug, whereas the latter could nicely be handled by pure Prolog predicates.

Note that nowadays, everything has been much simplified thanks to the new picat-style => handler for „procedural code“.

sin(x) is a very simple example. I can’t remember all the gory detail but in R you can have functions with arbitrary number of arguments, and you can define a function like foo(a, b, ...) (I think?) and pass the remaining arguments (the ones in the ellipsis) to a function you call in the definition of foo() by just using the ellipsis again (iirc).

This doesn’t take away from your argument but just to say that I can imagine a good reason to have arguments to an R function in a list (not for sin() maybe but in the general case).

EDIT: as I remember it, R was an asinine programming language. I remember being in constant awe+disgust when trying to read some library code, just to get some understanding of what it is meant to do. It also required pretty good understanding of statistics (with the specific vocabulary that goes with it) in order to even read and understand the documentation. I suspect it is the best free statistical environment out there which is why it is used.

I still don’t see that argument. foo(a,b,…) is easier to read and just as easy to process than rfunc(foo, [a,b,…]). Only when you map that to rfunc(foo, [a,b|Rest]), things may change as you cannot write foo(a,b|Rest) (would be fun).

In my view, if you need to represent something a human must type and read, you start with the syntax that suits your problem best, mostly meaning you can express everything you need and that looks natural and good. Next, if the format is not well suited for machine processing, you translate that into something that is suitable.

This principle b.t.w., brings us back to the start of this discussion: it is trivial to translate between the two representations of relations, so choose what looks best.

1 Like