Sunday 3 February 2008

Implementing Arc's function-negation operator in Python

Hey look! This guy implemented Arc's function-negation operator in Chicken. And he claims to be a Pythoneer, traitor ;-) !

I just investigated the possibilities of doing more or less the same thing in Python. Of course: you can't change the syntax, and you can't change default function's behaviour (which is not having "~" operator implemented). Or can't you?

Let's start with something more explicit for a moment. Nothing prevents us from creating a callable decorator that implements __invert__ something like this:

class invertible:

def __init__(self, foo):
self.foo = foo

def __call__(self, *args, **kwargs):
return self.foo(*args, **kwargs)

def __invert__(self):
def wrapper(*args, **kwargs):
return not self.foo(*args, **kwargs)
return wrapper
Implementing __init__ enables us to call invertible() with an argument, and implementing __call__ makes the instances of our class callable. These two work together in a way that an ordinary decorator works: It's a callable, that takes a callable and returns another callable - easy, huh?
Implementing __invert__ lets us use the "~" operator on the instances of our class, like this:
truth = invertible(lambda: True)
not_truth = ~truth
assert truth()
assert not not_truth()
This implementation, even though it's easy, is not finished yet. The inverted value is a regular function, which means, that we couldn't invert it again. The easy solution to that is decorating the wrapper:
class invertible:
# ...
def __invert__(self):
@invertible
def wrapper(*args, **kwargs):
return not self.foo(*args, **kwargs)
return wrapper
But this would cause the wrappers to accumulate on inverting. We can also check explicitly if we get an invertible on input and set a switch on each instance
class invertible:

def __init__(self, foo):
self.foo = foo
self.inverted = False

def __call__(self, *args, **kwargs):
if self.inverted:
return not self.foo(*args, **kwargs)
else:
return self.foo(*args, **kwargs)

def __invert__(self):
ret = invertible(self.foo)
ret.inverted = (not self.inverted)
return ret
Now, with the decorator ready, you can do some *evil* magic with frames and namespaces in order to decorate everything in scope, but I'm leaving it as an exercise to readers ;P

5 comments:

Tartley said...

Brilliant. Thanks for that. I wouldn't want to hose it all over production code, but it's awesome for getting me to think about how those features hang together. Thanks!

Tartley said...

(production code) ... unless it was particularly appropriate, that is, of course. :-)

Konrad said...

I wouldn't use it either. Introducing additional syntax doesnt' seem very pythonic to me :)

Adam Byrtek said...

I prefer a simple functional approach instead of writing opaque wrapper classes just to overload the ~ operator.

Try this closure (you can extend it to a proper decorator):

def complement(fn):
    def not_fn(*args, **kwargs):
        return not fn(*args, **kwargs)
    return not_fn

Konrad said...

@Adam
My aim was not to negate a function, but to enable the weird "~" operator usage. For the sake of fun.

BTW, what do you mean by extending to proper decorator - isn't it a decorator already?