A Crash Course in Logtalk

Or: Logtalk as she is spoke.

Since the last post was quite theoretical it’s only fair that the following ones will be more pragmatic in nature. The goal will be to develop a simplistic but still fairly capable text-adventure game in the same spirit as Zork and its successors and present the parts incrementally. But as you, the reader, probably knows compatibility between various Prolog systems is spurious at best. One remedy which I’ve championed before is Logtalk – an “open source object-oriented logic programming language” developed by Paulo Moura, which allows one to write portable programs without worrying about the details of the underlying Prolog system. As the description suggests, Logtalk leaves the constricted domain of a logic program and introduces a few new concepts that can greatly aid the programmer not only in large, but small and medium-sized projects. The question of what an object is in the context of logic programming is an interesting one. We shall however not concern us ourselves by giving any formal definitions and instead be rather descriptive. A formalistic viewpoint will probably be perused in a later entry, but for now the reader can refer to for example Logic and Objects, by Francis G. McCabe, which contains a thorough entry on the semantics of the Logic and objects language and answers what inheritance and objects mean in logic programming. Hence the remainder of this post will try to shed some light on the basics of the Logtalk language and explain how it’s installed and used. More advanced concepts will be explained as they are used when the time is ripe.

Installation

We begin our journey by downloading the Logtalk distribution. This can be done either via the Logtalk download page or by Subversion – the choice is yours! The next step is to read the exhaustive INSTALL.txt file, but for a POSIX system the procedure is as follows:

  • Set the correct environment variables. Logtalk uses two variables: LOGTALKHOME and LOGTALKUSER. For a single-user system these can coincide to the same directory, e.g. $HOME/lgtsvn, or wherever the distribution was unpacked. It’s also a good idea to augment the $PATH and $MANPATH variables to include $LOGTALKHOME/scripts, $LOGTALKHOME/integration and $LOGTALKHOME/man respectively.
  • Run the correct script in $LOGTALKHOME/integration. For SWI-Prolog this is swilgt.sh, for YAP Prolog it’s yaplgt.sh.

That’s it! Of course one may also digress in various customization aspects, but for basic usage this is more than enough.

Usage

Let’s hack away. I shall refrain from giving the idiomatic but ultimately quite meaningless Hello World-example and instead present something that uses at least some of the basic Logtalk facilities. The problem is the following: compute the bottom-up closure of a propositional logic program. If you’ve read the last entry on the semantics of logic programs you should immediately realize that this is nothing else than the least Herbrand model. We begin by outlining the algorithm:

  • The facts in the program are true.
  • If there is a rule H \leftarrow B_1, ,,, B_n such that all B_i are known to be true, then H must be true.
  • If it’s impossible to collect any new information, we’ve reached a fixpoint and are done.

The first step is to choose a program representation. Rules shall be represented as facts of the form rule(Head, Body), where Body is a list [B_1, ..., B_n] and Head and B_i are atoms. For facts, Body = [].  For example:

rule(a, []).
rule(b, [])

rule(c, [a,b])

Which should be read as: a and b are true, and if a and b are true then c must be true. This brings us to our first Logtalk concept: the object. The rule set discussed above could written as:

:- object(database).

    :- public(rule/2).

    rule(a, []).
    rule(b, []).

    rule(c, [a,b]).

:- end_object.

There’s nothing really fancy going on. The :-\, object directive obviously denotes the start of an object, while the :-\, end\_object directive denotes the end. public(rule/2) means that the set of rule/2 clauses have public visibility, i.e. that they can be accessed from anywhere. The only possible source of confusion might be the loose use of the word object. In e.g. Java or C++ there’s a sharp distinction between a class and an object. We say that an object is an instance of a class. This distinction is needed due to the fact that objects have explicit, dynamic state. In Logtalk, however, objects are most often used to provide module-like namespaces. (To be fair, there exists dynamic objects but they should be used sparingly, just like assert/retract in Prolog). Hence there’s often no need to make a distinction between a class and an object. For a more thorough explication, see the relevant part of the Logtalk manual.

Now let’s turn to the algorithm. From the pseudo-code we know that we’ll need a predicate that takes the current set of atoms known to be true and collects the heads of all the satisfied rules. This is easy to do: if we encounter a rule with the head H and body B, such that B is currently satisfied, then add H to the set of new inferences. Since there already exists a library for ordered sets in Logtalk we can write it as:

next(I0, I) :-
    setof(Head,
            (
             Body^(database::rule(Head, Body),
             satisfied(Body, I0),
             \+ set::member(Head, I0))
            ),
            New),
    set::union(I0, New, I).

The ::-operator denotes message sending. Hence, when we want to call a predicate defined in the database we write database::rule(...), but when we want to call a set predicate we prefix it with set.  I is the union of the old set of inferences, I0, and the new set of inferences, New. A head Head of a rule is added to New if its body is satisfied and it hasn’t been collected before. Since we’re only dealing with propositional atoms, the satisfied/2 predicate is easily implemented as:

satisfied(Body, Atoms) :-
    set::subset(Body, Atoms).

Next we need a predicate that repeatedly calls next/2 until no new inferences can be made. Since we use setof/3 in next/2, it will simply fail if New is the empty set. Hence the iteration only needs an if-then-else construct that either unifies its last argument with the obtained set of inferences, or calls itself recursively.

iterate(I0, Fixpoint) :-
    (   next(I0, I) ->
        iterate(I, Fixpoint)
    ;   Fixpoint = I0
    ).

To wrap it up it might be nice to have a predicate that takes a goal as argument, calls iterate/2 and checks whether the goal is a member of the fixpoint or not.

:- public(prove/1).

prove(Goal) :-
    iterate([], Fixpoint),
    set::member(Goal, Fixpoint).

Putting everything together

Logtalk programs are loaded with logtalk\_load/1 or logtalk\_load/2. Assuming that the bottom-up procedures were included in an object with the name bup, the whole project is loaded with the sequence:

logtalk_load(database). %The database.
logtalk_load(library(types_loader)) %The set library.
logtalk_load(bup) %The main program.

To run a query, one writes:

?- bup::prove(X).
X = a n
X = b n
X = c n
false.

And since it’s a mouthful to manually load the files every time it’s better to put that information in a loader file named loader.lgt.

:- initialization((
    logtalk_load(library(types_loader)),
    logtalk_load(database),
    logtalk_load(bup))).

The loader file can then be loaded from the top-level with either logtalk_load(loader) or the shorthand {loader}.

That’s it for now. I’ll admit that it would be nice to attach the full source code directory, but unfortunately the free WordPress hosting doesn’t seem to have that functionality. I managed to upload a zip-file by changing the file extension, but that hardly seems ideal. Rest assured that the matter will probably be solved before the next entry!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: