# HG changeset patch # User aurelien@trantor.local # Date 1258916257 -3600 # Sun Nov 22 19:57:37 2009 +0100 # Node ID 1d5f549da77a3866d48736a0d135471bfd0cef44 # Parent 0000000000000000000000000000000000000000 initial generic function/multimethods package based on David Mertz multimethods.py diff --git a/__init__.py b/__init__.py new file mode 100644 --- /dev/null +++ b/__init__.py @@ -0,0 +1,14 @@ +_METHS = {} + +from gf import dispatch as mm + +def method(*types): + "a method decorator on top of David Mertz's GF package" + def _wrap(meth): + name = meth.__name__ + gf = _METHS.get(name) + if gf is None: + gf = _METHS[name] = mm.GF() + gf.add_rule(types, meth) + return gf + return _wrap diff --git a/dispatch.py b/dispatch.py new file mode 100644 --- /dev/null +++ b/dispatch.py @@ -0,0 +1,51 @@ +def proximity(klass, mro): + return mro.index(klass) + +def lexicographic_mro(signature, matches): + "Use dispatch ranking similar to CLOS" + # Schwartzian transform to weight match sigs, left-to-right" + mros = [klass.mro() for klass in signature] + for (sig,func,nm),i in zip(matches,xrange(1000)): + matches[i] = (map(proximity, sig, mros), matches[i]) + matches.sort() + return map(lambda t:t[1], matches) + +#-- GF class +class GF(object): + def __init__(self, table=(), order=lexicographic_mro): + self.table = [] + for rule in table: + self.add_rule(*rule) + self.order = order + + def __call__(self, *args): + signature = [o.__class__ for o in args] + linearization = self.linearize_table(signature) + return linearization[0][0](*args) + + def add_rule(self, signature, func): + self.table.append((signature, func, 0)) + + def next_method(self): + func, _ = self.linearization[self.pos] + self.pos +=1 + result = func(*self.args) + self.pos -= 1 + return result + + def linearize_table(self, sig): + from operator import mul + len_match = lambda (s,f,nm): len(s) == len(sig) + typ_match = lambda (s,f,nm): reduce(mul, map(issubclass, sig, s)) + all_match = lambda x: len_match(x) and typ_match(x) + table = filter(all_match, self.table) + if not table: + def nomatch(*a): + raise TypeError, \ + "%s instance: no defined call signature <%s> for args (%s)" % \ + (self.__class__.__name__, + ",".join([str(o) for o in sig]), + a) + return [(nomatch,0)] + return map(lambda l:l[1:], self.order(sig, table)) + diff --git a/test/test_gf.py b/test/test_gf.py new file mode 100644 --- /dev/null +++ b/test/test_gf.py @@ -0,0 +1,21 @@ +import unittest +from gf import method +from gf.dispatch import GF + +class TestGF(unittest.TestCase): + + def test_method(self): + @method(int, int) + def foo(a, b): + return a+b + @method(str, str) + def foo(a, b): + return a+b + + self.assertEquals(foo(1, 2), 3) + self.assertEquals(foo('a', 'b'), 'ab') + self.assertRaises(TypeError, foo, 1, 'b') + +if __name__ == '__main__': + unittest.main() +