1d5f549da77a — aurelien@trantor.local 13 years ago
initial generic function/multimethods package based on David Mertz multimethods.py
3 files changed, 86 insertions(+), 0 deletions(-)

A => __init__.py
A => dispatch.py
A => test/test_gf.py
A => __init__.py +14 -0
@@ 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

          
A => dispatch.py +51 -0
@@ 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))
+

          
A => test/test_gf.py +21 -0
@@ 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()
+