Wednesday, March 30, 2005

Dylan style multiple dispatch

#generic.py
#multimethods in python
#
#Example:
#>>> @generic(int)
#... def reverse(next_func, n):
#...     return str(n)[::-1]
#... 
#>>> @generic(str)
#... def reverse(next_func, s):
#...     return s[::-1]
#... 
#>>>reverse("hello")
#'olleh'
#>>>reverse(1234)
#4321
#Changes:
#31-Mar-2005: Fixed a bug in the dispatch, which ment
#             that the best option might not be selected
#             multiple inheritence situations.

_genericmethods = {}

def _issuitable(pts, pts_):
    for pt, pt_ in zip(pts, pts_):
        if not issubclass(pt, pt_):
            return False
    return True

def _closer(pts, (fn1, pts1), (fn2, pts2)):
    for pt, pt1, pt2 in zip(pts, pts1, pts2):
        mro = pt.mro()
        score1 = mro.index(pt1)
        score2 = mro.index(pt2)
        if score1 < score2:
            return (fn1, pts1)
        if score2 < score1:
            return (fn2, pts2)
    return (fn1, pts1)
            
def generic(*paramtypes):
    def generic_(func):
        key = (func.__name__, len(paramtypes))
        functions = _genericmethods.setdefault(key, [])
        functions.append((func, paramtypes))
        def next_method(suitable, params, pts):
            if len(suitable)==0:
                raise Exception, "Unable to match generic method"
            else:
                best = suitable[0]
                for cur in suitable:
                    best = _closer(pts, best, cur)
                suitable = suitable[:]
                suitable.remove(best)
                func, pts_ = best
                return func(lambda *params:next_method(suitable, params, pts), *params)
        
        def first_call(*params):
            pts = [type(param) for param in params]
            suitable = [(fn_, pts_)
                        for fn_, pts_ in functions
                        if _issuitable(pts, pts_)]
            return next_method(suitable, params, pts)
        return first_call
    return generic_

Wednesday, March 09, 2005

Symbolic Expressions in python

#symbolic.py
#
#This is a python module to represent symbolic expressions
#
#>>>a = sym("a")
#>>>b = sym("b")
#>>>print eq(a + b, 5 * -b * b* b + 5)
#((a+b)=((((5*-b)*b)*b)+5))

class opp:
    """
    Base class for all operators and the symbolc class
    """

    def __getattr__(self, attr):
        if attr[:2] == "__" and attr[-2:] == "__":
            funcname = attr[2:-2]
            if funcname in operators:
                return lambda *x: operators[funcname](self, *x)
            elif funcname[0]=='r' and funcname[1:] in operators:
                return lambda x: operators[funcname[1:]](x, self)
        raise ("AttributeError: object '%s' has no attribute '%s'" %
               (repr(self), attr))

    def __coerce__(self, other):
        return None 

class sym(opp):
    """
    Symbols are used as variables in equations
    """

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return "sym("+self.name+")"

    def __str__(self):
        return self.name

class binopp(opp):
    """
    This is base class for defining binary operators
    """

    def __init__(self, lhs = None, rhs = None):
        self.lhs = lhs
        self.rhs = rhs

    def __str__(self):
        return "("+str(self.lhs)+self.operator+str(self.rhs)+")"

    def __repl__(self):
        return self.operator+"("+repl(self.lhs)+", "+repl(self.rhs)+")"    

class uniopp(opp):
    """
    This is the base class for unary operators
    """

    def __init__(self, value):
        self.value = value

    def __str__(self):
        return self.operator+str(self.value)

    def __repr__(self):
        return self.operator+"("+repr(self.value)+")"
    
#Operator definitions
class eq(binopp): operator = "="
class add(binopp): operator = "+"
class sub(binopp): operator = "-"
class div(binopp): operator = "/"
class mul(binopp): operator = "*"
class neg(uniopp): operator = "-"

operators = {"add":add, "sub":sub, "div":div,"mul":mul, "neg":neg}