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_

0 Comments:

Post a Comment

<< Home