Bill Mill web site logo

Python Generics, Guards, and Overloading

As I said in my last post, I've been looking erlang lately, and I'm very impressed with generic functions, pattern matching, and guards. As I read more code, I only like them more and more. I'm inclined to agree with Phillip that they "capture something really fundamental" about programming.

(disclaimer: I am new to this style of programming. I'm just thinking out loud. People have probably already had this discussion in more detail.)

As you know if you have been keeping up with Python blogs, Phillip has been working hard on adding the aforementioned features to Python by abusing decorators (hey, he admitted it). Here's what a generic factorial function can look like with his dispatch module:

While it's very cool that it works, I find it hard to imagine a less Pythonic syntax than this. Defining a new function name for each version of fact really hurts readability, and the guards become more prominent than the actual function definition. Compare it with the Erlang version, and its deficiencies become clearer:

So, if we want generics in Python, what would they ideally look like? Well, that question interests me, so I'm going to go into pie-in-the-sky mode and throw this out:

Isn't that pretty? In my imaginary world, fact(0) is automatically translated into a guard that only allows the first function to be called when n is 0. What are guards, and what do they look like in Bill-Mill-land? Some code should make it clear:

I don't think I even need to explain that code; if x and y are positive, the first function is called. Else, the second one is called. It just makes sense. If you guessed that the function fact(0) above would expand to fact(n) when n == 0: , then you're following along. More generally, any constant in the parameter list could be expanded by the interpreter into a guard.

This next example demonstrates both overloading, and the use of guards for dynamic type checking. Imagine that this follows immediately after the previous code sample:

The first function accepts one argument, a 2-tuple, and unpacks it to the variables x and y . If y can be adapted to an int , then the function is called. If none of the more specific move_pointer functions can be found, then the default, move_pointer(*args) would be called. If no default were found, an exception would be raised.

Basically, what I'm dreaming of in this post is stealing some elegant syntax for Phillip's work from Erlang and porting it to Python. I think that generics, guards, and overloading are a better addition to Python than is static typing, bringing some of the benefits that have been proposed for static typing, without the drawbacks.

UPDATE: Added return statements to the first "imaginary Python" code sample. I never meant to leave them off. Thanks to Beat Bolli for pointing this out.