Gratis bloggen bei

Locic Programming in Python - PyLog

PyLog by Christophe Delord



There are two PyLogs out there. One is a bridge to swi-prolog, the other one allows entering und unifying "Terms" using python syntax. This is the one I am talking about here.It is a singly .py file containing less than 700 lines of code.

PyLog uses python syntax, i.e. it does not parse Strings. Instead it implements

  • __init__(),
  • __call__() and
  • __iter__()

in clever ways, so you can write prolog semantics using python syntax.


There is a base class "Term" in PyLog from which you can subclass your own functors. You can e.g. write:

  • class f(Term):pass
  • class g(Term):pass
  • aTerm = f(g(1))

In the above exampte f and g are classes and when you write g(1) the class's __init__() method is invoked and you get a g object. It internally remembers:

  • its functor (which is equal to its class  (here the class g - not its class name !),
  • the arguments you passed ("1" in this example) and
  • its arity (which is the number of arguments passed - 1 in this case).

The cool thing here is that no extra parser is needed. If you stored your Terms in Strings you would have to parse the Strings. Here the syntax is strictly pyhon.

At the bottom line you now have a way to construct Terms from class names, round brackets and commas and python itself checks that the syntax is correct.

Unifying Terms

You can see a Term as a tree and unifying two terms is putting two trees on top of each other and see if they match. It only becomes interesting, because a Term can contain unbound variables. This is like having a tree node which says "anything could be here". When unifying two Terms PyLog may replace Variables by terms. Unifying

  • f(X) with (f(f(1))

would bind X to f(1). If there are variables on both sides as when unifying

  • f(X) with f(Y)

PyLog would bind X to Y making them equal even though none of them has a value yet.

There is a Unify class (which is a subclass of Term) and you can do unifications using the following code:

for s in Unify(f(X), f(f(1))):
    print s(X)

This prints:

  • f(1)

The Stack

The only class which is not derived from Term is Stack. It maintains a stack of variable bindings. If you call a Stack object with a variable you will get the value of that variable if it has a binding. So

s(X) return the value of X if X is a bound variable (otherwise it returns the unbound variable a _1, _2 etc.)

Unifcation is first of all a method of Stack. You can call aStack.unify(T1, T2) which will yield a new Stack object, So calling

s = Stack()
for s1 in s.unify(f(X), f(f(1))):
    print s1(X)

prints: f(1)

There is also a Unify class which is a subclass of Term. So

  • Unify(f(X), f(f(1))) is a Term.

When you print this Term you get

  • Unify(f(_1),f(f(1)))

When you call this Term you need to pass it a Stack object and you get a generator which yields Stack objects as the result.

When you iterate over the result you will get Stack objects.

When you write for s in Unify(T1, T2) you will first get a Term and then this  Term gets iterated. The __iter__() method in Term calls the term with a new Stack object:

  • def __iter__(self): return self(Stack())

So iterating over the Unify Term is the same as calling it with a new Stack object. Not all Terms have a __call__ method but Unify does. It looks like this:

    def __call__(self, s):
        return s.unify(*self.args)

The s is our newly created Stack object. *self.args are the two Terms we want to unify.  Again the result is one Stack object for each iteration. 

Example Programs

The uncle program implements the knowledge that a person has an uncle if this person has a parent and this parent has a brother. So we need to be able to talk about parents and brothers first.

 class brother(Term2):
    def __call__(self, s):
        X,Y = self.args
        for b1,b2 in (
            ("pit", "john" ),
            ("pit", "james" ),
            ("jim", "paul" )
            for s1 in s.unify(brother(X,Y),brother(b1,b2)):
                yield s1
            for s1 in s.unify(brother(X,Y),brother(b2,b1)):
                yield s1

What we're doing here is yielding Stack objects which bind the free variable to pairs of person names. Note that we bind both ways, because if A is a brother of B then B is a brother of A. In order to call Stack.unify() we must pass it the two Terms to be unified.

Now a Variable is a Term too and we could bind both variables one by one, but this looks ugly and we need a nested loop. Instead we create a new Term brother(X,Y) which can be seen as brother(*self.args) and unify the entire Term with brother(b1,b2). The functor brother does not really matter here, we could have used f(X,Y) just as well, but brother is the only functor we can be certain it exists.

I am not too happy about the way I have to write brother() because a lot of the code has nothing to do with the original problem. The two loops seem like an implementation detail. Likewise the fact that the two variables can be found on the stack. Maybe there is a more concise way of writing this or maybe we should implement a common base class for Fact

You can write parent in the same way, except you only need one loop, because if A is parent of B then B is not a parent of A.

Finally you can write uncle as

class uncle(Term2):
    def __call__(self, s):
        _ =Var()
        X,Y = self.args
        return  (parent(X,_) & brother(_,Y))(s)

 The Term  parent(X,_) & brother(_,Y) is - well - a Term. In order to bind Variables you have to call this Term with a Stack object as parameter.

 To get all uncles you have to iterate as follows:

 def uncleExample():
    X = Var()
    Y = Var()

    for s1 in uncle(X,Y):
        print s1(X),s1(Y)


This is about as far as I got exploring PyLog. I did non manage to locate the Prolog engine, which was supposed to be included. I have the impression, that Christophe Delord once had a more complete albeit buggy version and then stripped out everything which did dot work 100%. Someone said that it easy to write an engine once you got the unification right, so maybe this applies to PyLog too.

In any case I like the approach of usiging python syntax and python 1st class objects. This offers a lot of possibilities. But the there is no engine. If you are a python programmer and not an AI expert PyLog may be too incomplete for you. OTOH the fact that everything is pythons offers a lot of possibilities.

1.5.09 09:03


bisher 0 Kommentar(e)     TrackBack-URL

E-Mail bei weiteren Kommentaren
Informationen speichern (Cookie)

 Smileys einfügen

Verantwortlich für die Inhalte ist der Autor. Dein kostenloses Blog bei myblog.de! Datenschutzerklärung