DEV Community

Cover image for Prolog first steps: predicates, metapredicates, lambdas

Prolog first steps: predicates, metapredicates, lambdas

I first became interested in Prolog after I watched a talk by Joe Armstrong, creator of Erlang, in which he mentioned this small, niche language twice. The first time, he explains how he took inspiration from the Prolog syntax to write Erlang's. The second, he mentions that if he were to choose only 4 languages to learn, Prolog would be among them.

I have always found it super instructive to learn languages that are very distinct from one another. There isn't much to learn from a third dynamically-typed imperative language. But coming from, say, imperative to fuctional or, in this case, logical paradigm is quite the mind-bending experience. It makes us see things we think we know from a different angle, and provides new ways of expressing ideas and operations.

This is my first contact with logical programming, so everything here is basic and rudimentary. It is a first step in learning how logical programming works and why it is interesting.

Deductions: logical results

Prolog provides a very interesting functionality, one that I haven't seen in other languages. While all languages provide results for problems such as,

Let X = A, Y = B, what is the result R of X + Y ?

Prolog provides a way to find all possible values of variables that make a problem true. For example,

Let X + Y = R, what are the possible values of X and Y for which R = C

Predicates: defining our data set

In Prolog, we define relations between terms. These relations are called predicates, statements that evaluate to true. Let us define some.

% astrology.pl 

ruler(aries, mars). 
ruler(taurus, venus). 
ruler(gemini, mercury). 
ruler(cancer, moon). 
ruler(leo, sun). 
ruler(virgo, mercury). 
ruler(libra, venus). 
ruler(scorpio, mars). 
ruler(sagittarius, jupiter). 
ruler(capricorn, saturn). 
ruler(aquarius, saturn). 
ruler(pisces, jupiter). 

element(taurus, earth).
element(virgo, earth).
element(capricorn, earth).
element(aquarius, air).
element(libra, air).
element(gemini, air).
element(pisces, water).
element(cancer, water).
element(scorpio, water).
element(aries, fire).
element(sagittarius, fire).
element(leo, fire).
Enter fullscreen mode Exit fullscreen mode

Undefined possible relations such as ruler(aries, pluto) evaluate to false.

Queries: deducting results from the data set

Let us generate some new information from the data we have, using logical deduction.

Get a value from relations

Suppose we have two friends whose star sign we do not know, but for whom we can imagine, from their personality, their ruler planets and elements. We can deduce their signs based on those traits.

To extract values from queries, we use variables, terms starting with an uppercase letter.

?- element(Sign, fire), ruler(Sign, sun).
Sign = Leo.
Enter fullscreen mode Exit fullscreen mode

Alfred is has a fiery spirit and a sunny personality. By deduction, his sign is no other than Leo.

?- element(Sign, water), ruler(Sign, mars).
Sign = scorpio.
Enter fullscreen mode Exit fullscreen mode

Beth loves the ocean, and is very combative, like the god Ares. By deduction, her sign has to be scorpio.

?- element(Sign, water), ruler(Sign, venus) .
false.
Enter fullscreen mode Exit fullscreen mode

Charles also loves the ocean, and is beautiful like Aphrodite. But there is no sign that fits this combination. Our assumptions about him must be wrong.

Get multiple valid results from relations

In our premises, we have not defined any direct relation between planets and elements. However, planets rule signs, therefore we can establish indirect relations. For example, we can ask for planets that have the earth element; in other words, "what is the planet X that rules some sign Y of the earth element?"

?- element(Y, earth), ruler(Y, X).
Y = taurus,
X = venus ;
Y = virgo,
X = mercury ;
Y = capricorn,
X = saturn.
Enter fullscreen mode Exit fullscreen mode

Prolog is telling us that Venus, Mercury, and Saturn are ruled by earth signs (taurus, virgo, capricorn). It is noteworthy in our dataset, while a single sign has a single element (1-n), a single planet may have multiple elements (n-n).

?- element(Y, air), ruler(Y, X).
Y = aquarius,
X = saturn ;
Y = libra,
X = venus ;
Y = gemini,
X = mercury.
Enter fullscreen mode Exit fullscreen mode

We see that Saturn and Mercury are also air planets in this world.

Get a list of valid results

As we saw, a planet may have multiple elements based on its ruled signs. Let us try now to get the elements of a certain planet. This time, we will wrap those in a list, using the buit-in predicate findall/3.

elem(El, Planet) :-
    element(Sign, El), ruler(Sign, Planet).

?- findall(El, elem(El, mercury), Elements).
    Elements = [earth, air].
Enter fullscreen mode Exit fullscreen mode

First, we wrap the same query for elements of some planet from the previous topic in a function (more precisely, in a conditional predicate). Functions in Prolog are Horn clauses, a form of implication that reverses the position of p -> q to q <- p, or "q if p", in natural language.

Our Horn clause states that, "E is the element of a planet P if E is the element of a sign S, and the sign S is the ruler of the planet P".

Then we find the elements of the planet Mercury, and throw those in a list. The built-in predicate findall/3 evaluates all elements for which P(El, ...) is true, and appends them to a list of valid elements.

One amazing feature of Prolog is that, because it deals with values that evaluate to two, the elem/2 serves not only to find elements of a planet, but also planets of an element! In other words, we can query both elem(E, mercury) and elem(earth, P), and both return the valid results that we look for. It is a feature I have never seen in a language.

Metapredicates

Metapredicates are the logical programming equivalents of higher-order functions in functional programming. They are predicates that have predicates as arguments. The predicate findall/3 mentioned earlier is such an example.

Metapredicates are very useful generalizations on common operations. We may go beyond the metapredicates already predefined in the standard library, and define our own.

For example, suppose we want to judge whether all planets in a list of planets are rulers of some sign. We may define a predicate all_rulers, such as:

all_rulers([]).
all_rulers([P|Ps] :-
    ruler(_, P),
    all_rulers(Ps). ?- all_rulers([mercury, sun]).
true

?- all_rulers([mercury, pluto, sun]).
false
Enter fullscreen mode Exit fullscreen mode

The predicate is defined recursively, and states that all planets are rulers if the first element of the list is a ruler, and all other elements are also rulers.

Judging whether all elements in a list are true according to some condition is so useful that we would be wise to define a general operation that accepts not just ruler/2, but any condition.

ruler(P) :- ruler(_, P)

all([], _).
all([E|Es], Pred) :-
    call(Pred, E),
    all(Es, Pred).

?- all([mercury, sun], ruler).
true
Enter fullscreen mode Exit fullscreen mode

Lambdas

We have an inconvenient limitation in our program: all/2/ only accepts as arguments predicates with arity 1 (i.e. that take one argument). That is becase we make use of call(Pred, E), which applies the current element to the predicate.

To further generalize our metapredicate, we can make use of lambdas, anonymous predicates that allow us not only to apply predicates of different arities, but also to define predicates on runtime.

Let's define another metapredicate any, which judges whether any element in a list is valid according to a condition.

any([], _) :- false.
any([E|Es], Pred) :-
    call(Pred, E);
    any(Es, Pred).

?- pack_install(lambda).
true.

?- use_module(library(lambda)).
true.

?- any([pluto, mercury, uranus], \X^ruler(_,X)).
true

?- any([pluto, uranus], \X^ruler(_,X)).
false.

Enter fullscreen mode Exit fullscreen mode

The metapredicate any/2 is very similar to all/2, except that instead of using AND operators (,), it uses OR operators (;); in other words, any/2 is true if the predicate for the first element of the list is true, or if it is true for some other subsequent element.

This time, we are not passing a predicate previously defined in compilation, but rather a dynamic predicate with a lambda (we install and require the lambda package first). Lambdas empower us to call a 2-arity predicate such as ruler/2 by wrapping it in an anonymous 1-arity anonymous predicate.

Conclusion

Prolog is an interesting language, with a mind-bending paradigm. In this article, we have explore some of its features, such as:

  • getting bidirectional results;
  • finding multiple solutions to a problem using variables;
  • defining functions as Horn clauses;
  • generalizing particular predicates into metapredicates;
  • stating anonymous predicates with lambdas.

I hope you liked this article, and I look forward to exploring Prolog further. I have just started using this language, so I apologize for any incorrect terminology or explanations. If you have any comments or suggestions, I'll be happy to read them.

Top comments (3)

Collapse
 
adolfont profile image
Adolfo Neto

I didnt know Prolog had anonymous functions!

Collapse
 
escribapetrus profile image
P. Schreiber ๐Ÿง™๐Ÿปโ€โ™‚๏ธ๐Ÿ”ฎ๐Ÿ

It took me some time to figure it out, but I found two libraries that provide this feature: lambda and yall. I chose the former, but I think yall is included in the standard library (I might be wrong).