|
Description:
Some of Python's powerful meta-programming features are used to enable writing Python functions which include Prolog-like statements. Such functions load a Prolog-like database. When coupled with a suitable inference engine for logic databases, this is a way to add logical programming -- the last unsupported major paradigm -- to Python. Start at the bottom of the code for an example of the enabled syntax.
Source: Text Source
import string
class Struct:
def __init__(self, database, head, subs):
"""
The head and subs are essential - what makes this struct.
The database should only be used while structs are constructed,
and later removed.
"""
self.database = database
self.head = head
self.subs = subs
def __pos__(self):
"""
unary + means insert into database as fact
"""
self.database.add_fact(self)
def __lshift__(self, requisites):
"""
The ideal is
consequent(args) << cond1(args1),...
for now we must do with
consequent(args) << (cond1(args1),...)
"""
self.database.add_conditional(self, requisites)
def __str__(self):
subs = map (str, self.subs)
return str(self.head) + "(" + string.join(subs,',') + ")"
class Symbol:
def __init__ (self, name, database):
self.name = name
self.database = database
def __call__ (self, *args):
return Struct(self.database, self, args)
def __str__(self):
return self.name
class Constant(Symbol):
"""
A constant is a name. Its value is its name too.
"""
def value(self): return self.name
class Variable(Symbol):
def __str__(self):
return "?"+self.name
def symbol(name, database):
if (name[0] in string.uppercase):
return Variable(name,database)
else:
return Constant(name, database)
class Database:
def __init__(self):
self.facts = []
self.conditionals = []
def add_fact(self, fact):
self.facts.append(fact)
def add_conditional(self,head,requisites):
if not(isinstance(requisites, list)):
requisites = [requisites]
self.conditionals.append((head,requisites))
def prt(self):
"""
Print the database in somewhat readable (prolog) form
"""
for f in self.facts: print f, "."
for (h,r) in self.conditionals:
print h, ":-", string.join(map(str,r), " , "), "."
def consult(self, func):
"""
Include definitions from func into database
"""
try:
code = func.func_code
except:
raise TypeError, "function or method argument expected"
names = code.co_names
locally_defined = code.co_varnames
globally_defined = func.func_globals.keys()
defined = locally_defined+tuple(globally_defined)
undefined = [name for name in names if name not in defined]
newglobals = func.func_globals.copy()
for name in undefined:
newglobals[name] = symbol(name, self)
exec code in newglobals
def consult_and_transform(self, func):
"""
A helper for decorator implementation
"""
self.consult(func)
return LogicalFunction(self, func)
class LogicalFunction:
"""
This class replaces a logical function once it has
been consulted, to avoid erroneous use
"""
def __init__(self, database, func):
self.database=database
self.logical_function=func
def __call__(self):
raise TypeError, "Logical functions are not really callable"
def logical(database):
"""
A decorator for logical functions
"""
return database.consult_and_transform
if __name__ == "__main__":
db = Database()
global_var = ["known", "fact"]
print "Defining a logical function...",
@logical(db)
def prolog_func():
+ farmer(moshe)
+ donkey(eeyore)
beats(X,Y) << [ farmer(X), donkey(Y), owns(X,Y) ]
x = "'local value of x'"; y = 17
+ globally(global_var)
equal("x","y") << equal(x,y)
print "Done."
print "Definition has already updated the database as follows:"
print
db.prt()
print
print "Trying to call the logical function raises an error:"
print
prolog_func()
Discussion:
Python is widely acclaimed for supporting many programming paradigms; you can write procedural code, object oriented code, functional code, and thanks to metaclasses, even aspect oriented programming is not hard. However, Python has no support for the logical programming paradigm; this recipe aims to bring Python a little closer there.
Start at the bottom of the source. The goal of this exercise is to enable the writing of functions like prolog_func(), where a collection of facts and rules can be written in a language reminiscent of Prolog and First-Order Logic. These facts and rules are collected into a database, where an inference engine can later use them to answer queries (the inference engine and query interface are out of scope here, and left out).
Putting logical inference code into Python has been done before, e.g. http://christophe.delord.free.fr/en/pylog/. But Pylog makes it relatively hard to use Python objects from the Prolog code (you can, but not using Prolog syntax). This is the problem Pythologic solves.
The "magic" is divided between Database.consult(), which turns all undefined names in the function to logical symbols, and the overloaded operators in the Struct and Symbol classes.
This recipe has some problems:
First of all, it is wildly unpythonic, in its abusive overhaul of the function semantics. At a more detailed level, the function is called upon definition (which would typically mean during import), which may cause surprising problems with respect to normal Python code in it. The function is called with a specially-constructed global environment, which means assignments to globals will not take effect (this is quite easy to fix, but would clutter the code somewhat).
For perfect integration, Python callables appearing in Prolog rules should not be called until the rules are evaluated (and then, should be called with values obtained from logical variables). The recipe does not support this -- this would require some pretty deep code transformations, and this is a proof-of-concept only. If anyone chooses to go there, I would also suggest removing the unary plus requirement.
Still, I think it is an interesting, and (if I may say so myself) thought-provoking example of how far a little meta-programming can take you.
Although this recipe uses a @decorator, and therefore applies only to Python 2.4, it is very easy to follow instructions in the code to make it work with older Pythons; this should work even down to 1.5.2, though I haven't tested it.
|