Functional code in python (or yet another abuse of python)

Thu, 09 Feb 2006 10:54:18 +0000
tech python article

So I was inspired (distracted) by the python functional programming module, and got to thinking couldn't things like partial application and function composition be a bit more natural in the syntax.

So it turns out that with decorators and a bit of evil introspection I came up with something that works (at least for functions that don't have keyword arguments). So you can do something like:


@Functional
def add(a, b): return a + b

add3 = add.add

assert(add3(1)(2)(3) == add3(1, 2, 3) == add3(1, 2)(3) == add3(1)(2, 3) == 6)

So what is the magic (or evilness)? Well first we (ab)use decorators to decorate things as Functional, which is you probably know, is just a shortcut for function = Functional(function).

The evil is in the Functional class:


class Functional:
    def __init__(self, fn, nargs = None):
        self.fn = fn
        if not nargs:
            _args, _, _, _ = inspect.getargspec(self.fn)
            self.nargs = len(_args)
        else:
            self.nargs = nargs

    def __call__(self, *args, **kargs):
        if len(args) > self.nargs:
            res = self.fn(*args[:self.nargs], **kargs)
            if res.__class__ == Functional:
                return res(*args[self.nargs:])
            if type(res) != type((0,)):
                res = (res, )
            return res + args[self.nargs:]

        elif len(args) == self.nargs:
            return self.fn(*args, **kargs)
        else:
            def newfunc(*fargs, **fkeyw):
                return self.fn(*(args + fargs))
            newfunc.func = self.fn
            newfunc.args = args
            return Functional(newfunc, self.nargs - len(args))

    def __getattr__(self, name):
        if hasattr(self.fn, name):
            return getattr(self.fn, name)
        func_1 = self.fn
        func_2 = globals()[name]
        def composition(*args, **kwargs):
            res = func_2(*args, **kwargs)
            if type(res) != type((0,)):
                res = (res, )
            return self(*res)
        return Functional(composition, func_2.nargs)

I totally abuse the __getattr__ method so that dot becomes a composition operator. This returns a new function (which is also Functional), which when called will pass on the return value from the first function to the second function. If the first function returns multiple results each of these is passed as arguments to the second function.

The real magic comes in the overload __call__, which is where partial functions are hacked in. Basically if not enough arguments are passed to the function is returns a new function that accumulates the arguments already passed and once it gets enough calls the original function. Of course the function returned from partial application is itself. The real evil is in supporting the case add3(1, 2, 3) which means we detect if too many arguments are passed and then call with only the first arguments, then if the called function returns another functional we apply the remaining arguments to it.

Oh yeah, I wouldn't use this in any real python code, as it is likely to confuse everyone!

blog comments powered by Disqus