[RFC] Parametric Polymorphism

Bengt Richter bokr at oz.net
Mon Sep 26 13:07:49 EDT 2005


On Sun, 25 Sep 2005 09:30:30 +0100, Catalin Marinas <catalin.marinas at gmail.com> wrote:

>Hi,
>
>Sorry if this was previously discussed but it's something I miss in
>Python. I get around this using isinstance() but it would be cleaner
>to have separate functions with the same name but different argument
>types. I think the idea gets quite close to the Lisp/CLOS
>implementation of methods.
>
>Below is just simple implementation example (and class functions are
>not supported) but it can be further extended/optimised/modified for
>better type detection like issubclass() etc. The idea is similar to
>the @accepts decorator:
>
>
>methods = dict()
>
>def method(*types):
>    def build_method(f):
>        assert len(types) == f.func_code.co_argcount
>
>        if not f.func_name in methods:
>            methods[f.func_name] = dict()
>        methods[f.func_name][str(types)] = f
>
>        def new_f(*args, **kwds):
>            type_str = str(tuple([type(arg) for arg in args]))
>            assert type_str in methods[f.func_name]
>            return methods[f.func_name][type_str](*args, **kwds)
>        new_f.func_name = f.func_name
>
>        return new_f
>
>    return build_method
>
>
>And its utilisation:
>
>@method(int)
>def test(arg):
>    print 'int', arg
>
>@method(float)
>def test(arg):
>    print 'float', arg
>
>test(1)                         # succeeds
>test(1.5)                       # succeeds
>test(1, 2)                      # assert fails
>test('aaa')                     # assert fails
>
>
>Let me know what you think. Thanks.
>
I am reminded of reinventing multimethods, but an interesting twist, so I'll add on ;-)
The following could be made more robust, but avoids a separately named dictionary
and lets the function name select a dedicated-to-the-function-name dictionary (subclass)
directly instead of looking it up centrally with two levels involved.

Just thought of this variant of your idea, so not tested beyond what you see ;-)

 >>> def method(*types):
 ...     def mkdisp(f):
 ...         try: disp = eval(f.func_name)
 ...         except NameError:
 ...             class disp(dict):
 ...                 def __call__(self, *args):
 ...                     return self[tuple((type(arg) for arg in args))](*args)
 ...             disp = disp()
 ...         disp[types] = f
 ...         return disp
 ...     return mkdisp
 ...
 >>> @method(int)
 ... def test(arg):
 ...     print 'int', arg
 ...
 >>> test
 {(<type 'int'>,): <function test at 0x02EEAE2C>}
 >>> @method(float)
 ... def test(arg):
 ...     print 'float', arg
 ...
 >>> test(1)
 int 1
 >>> test(1.5)
 float 1.5
 >>> test(1, 2)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 7, in __call__
 KeyError: (<type 'int'>, <type 'int'>)
 >>> test('aaa')
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
   File "<stdin>", line 7, in __call__
 KeyError: <type 'str'>
 >>> test
 {(<type 'int'>,): <function test at 0x02EEAE2C>, (<type 'float'>,): <function test at 0x02EEAE64>}

You could give it a nice __repr__ ...

Hm, I'll just cheat right here instead of putting it in the decorator's class where it belongs:

 >>> def __repr__(self):
 ...     fname = self.values()[0].func_name
 ...     types = [tuple((t.__name__ for t in sig)) for sig in self.keys()]
 ...     return '<%s-disp for args %s>' % (fname, repr(types)[1:-1])
 ...
 >>> type(test).__repr__ = __repr__
 >>> test
 <test-disp for args ('int',), ('float',)>
 >>> @method(str, str)
 ... def test(s1, s2): return s1, s2
 ...
 >>> test
 <test-disp for args ('int',), ('str', 'str'), ('float',)>
 >>> test('ah', 'ha')
 ('ah', 'ha')
 >>> test(123)
 int 123

That __repr__ could definitely be improved some more ;-)

Regards,
Bengt Richter



More information about the Python-list mailing list