BitWorking

This is Joe Gregorio's writings (archives), projects and status updates.

Python isn't just Java without the compile

I've had several conversations recently where it's become clear to me that some people view dynamic languages like Python and Ruby as just Java without the compile step. Yes, one of the advantages of a dynamic language is the ability to drop the compile from the edit/compile/run cycle, but there is much more to it than that.

[Update: Some corrections. A first-class function isn't one that is just defined outside a class, but is an object itself. I didn't state that explicitly but I did show that in the worked example at the end. Python doesn't have true continuations, what I labelled continuations are actually just generators, a limited form of continuation. ]

Now let's not start a language war here; to be perfectly clear, I'm not calling Java a bad language nor am I impugning the skills or mental prowess of Java programmers, what I want to do is just introduce some of the abstractions that are present in Python and Ruby that are missing from Java. Below is a partial list of such abstractions. To give proper credit, this list has a lot of overlap with a similar list that appears in Bruce Tate's Beyond Java.

  1. First-class functions
  2. Keyword parameters
  3. Default parameters
  4. Tuples
  5. Parallel assignment
  6. Efficient multiple return values
  7. Continuations
  8. User-defined operators
  9. Closures
  10. Meta-programming

Let's look at each of these abstractions in detail in Python. For the uninitiated, '>>>' is the prompt for the Python interpreter, where these examples were run. An interactive interpreter is another feature of Ruby and Python that isn't listed above and can be an incredible tool for testing out code, debugging, and doing exploratory programming.

First-class functions

You can define a standalone function, it doesn't have to be a member of a class.

>>> def f(a, b):
	return a + b

>>> f(3, 4)
7

Keyword parameters

Parameters can be specified by name and not just order.

>>> f(b=5, a=7)
12

Default parameters

Defaults can be supplied for parameters.

>>> def f(a, b=100):
	return a + b

>>> f(2)
102

Parallel assignment using tuples

A tuple is an immutable list and can be used in assignment.

>>> a = 1
>>> b = 2
>>> (c, d) = (a+b, a-b)
>>> c
3
>>> d
-1

Efficient multiple return values

Tuples can also be used to return multiple values.

>>> def f(a, b):
	return (a-b, a+b)
>>> f(2,3)
(-1, 5)

Continuations

Continuations save and restore the execution of a function.

>>> def f(a, b):
	while 1:
		(a, b) = (a-b, a+b)
		yield (a,b)

		
>>> x = f(1,2)
>>> x.next()
(-1, 3)
>>> x.next()
(-4, 2)
>>> x.next()
(-6, -2)

Closures

>>> def too_big(limit):
        def compare(x):
            return x > limit
        return compare

The variable 'limit' lives on beyond the scope of too_big().

>>> f = too_big(100)

>>> f(100)
False
>>> f(101)
True

Python has other notations for creating closures, such as the list comprehension shown below.

>>> LIMIT=30
>>> [n for n in range(10) if n*n > LIMIT]
[6, 7, 8, 9]

Meta-programming

Create a class

>>> class A:
        b = 3

Create an instance of that class

>>> a = A()
>>> a.b
3

Create a first-class function

>>> def f(self, a):
        return a + self.b

Install that function in the class

>>> A.fred = f

Call that function on the instance of A()

>>> a.fred(4)
7

Putting it all together

Here is a worked example showing how powerful these abstractions can be when applied together. One of the parts of Django is the URI dispatcher. It takes the incoming URI and uses that to look up which object should handle the request.

For example we could have two view functions.

def entry_view(id):
    print "View %s" % id


def collection_view(id, entry_id = "2"):
    print "Collection %s %s" % (id, entry_id)

And we may want to dispatch control to each function based on what the request URI looks like:

selector = [
    ("/entry/(?P<id>\d+)", entry_view),
    ("/collection/(?P<id>\w+)/(?P<entry_id>\d+)", collection_view)
    ]

It would be nice to have a function 'dispatch' that acted as follows, given the above mapping of regular expressions and functions, find the regular expression that matches and then pull out the value from the matching string and pass them to the callable object. That is, if we get a request to the URI "/entry/3" the function 'entry_view' should get called with a parameter of 'id=3'.

>>> dispatch(selector, "/entry/3")
View 3

>>> dispatch(selector, "/collection/main/27")
Collection main 27

So how much code does it take to implement dispatch?

import re

def dispatch(selector, uri):               #1
    for (regex, callable) in selector:     #2
        match = re.search(regex, uri)
        if match:
            callable(**match.groupdict())  #3
		  break

Note the use of (#1) first class functions, (#2) tuples, parallel assignment, and (#3) keyword arguments. The statement on (#3) needs a little more explanation; match.groupdict() returns a dictionary that maps the named parameters in the regular expression to their value in the string; the '**' says to expand that dictionary and used it as a source of named parameters for the function invocation. Also note that don't have to use just first-class functions as the target of our dispatching. Any callable object can used, including an object member function.

As you can see Python, like many other dynamic languages, has a powerful set of abstractions that aren't available in Java. What do I expect you to do with this new found knowledge? Dump Java? No. Wring your hands in angst over these gaps in Java? No. Immediately rewrite your current project in Python? No. What I would ask you to do is keep an open mind and keep your eye on dynamic languages, they're more than just Java-without-the-compile.

2006-08-30