Implementations of Multi-Methods and Tail Call Elimination (Or how I stopped worrying and learned to love decorators)

Jack Diederich jack at performancedrivers.com
Sun Aug 29 19:52:49 EDT 2004


On Sun, Aug 29, 2004 at 03:59:17PM -0700, Stephen Thorne wrote:
> Multimethods come first, because I wrote them first. I don't really
> like the implemetation - I'm sure it can be improved. I especially
> dislike having to use the @staticmethod decorator, and having to have
> the functions in alphabetical order.
> 
> class fact(MultiMethod):
>     @when(lambda x:x==0)
>     @takes(int)
>     @staticmethod
>     def a(x):
>         return 1
> 
>     @when(lambda x:x>0)
>     @takes(int)
>     @staticmethod
>     def b(x):
>         return x * fact(x-1)
> 
>     @staticmethod
>     def c(x):
>         raise ValueError("Invalid argument to fact()")
> fact = fact()

@takes seems like an extra version of @where

> Okay. Lots of sugar required to do it, but look! its a multimethod!
> fact(10.1) raises an error, fact(0) == 1 and fact(5) == 120.
> 
> The implementation looks like this:
> def takes(*arg, **kwargs):
>     def _(f):
>         def check(*x, **xs):
>             def test(a,b):
>                 return b is None or isinstance(a,b)
>             if len([True for (a,b) in zip(x, arg) if not test(a,b)]):
>                 raise NextMethodException()
>             if len([True for (a,b) in kwargs.keys() if not
> test(xs.get(b,None),lb)]):
>                 raise NextMethodException()
>             return f(*x, **xs)
>         return check
>     return _
> 
> def when(f):
>     def _(d):
>         def check(x):
>             if not f(x):
>                 raise NextMethodException()
>             return d(x)
>         return check
>     return _
> 
> class NextMethodException(Exception):
>     pass
> 
> class MultiMethod:
>     def __call__(self, *arg, **kwargs):
>         for i in dir(self):
>             if i[0] == '_': continue
>             try:
>                 return getattr(self, i)(*arg, **kwargs)
>             except NextMethodException: pass
> 
> Okay, thats multimethods. I really don't like the implementation all
> that much, I'm sure there's a better way to do it than using
> exceptions like that. Suggestions?

This screams for a stack frame hack, so here it is.

import inspect
def multi(when=None):
  def build(func):
    try:
      # get the existing version of our func to fall back on
      try_instead = inspect.currentframe(1).f_locals[func.__name__]
    except KeyError:
      try_instead = None
    if (when is None):
      return func
    # else, cascade
    def _func(*args, **opts):
      if (when(*args, **opts)):
        return func(*args, **opts)
      else:
        return try_instead(*args, **opts)
    return _func
  return build

@multi(lambda x:x == 0)
def fact(x):
  return 1

@multi(lambda x:x > 0)
def fact(x):
  return x * fact(x-1)

print fact(0)
print fact(100)

Just because it is possible doesn't make a good idea *wink*,

-Jack



More information about the Python-list mailing list