Wednesday, April 22, 2009

I have finally gotten back to working on interface injection in OpenJDK. As before I get a few breaks when I wait for stuff to compile. These breaks are too long to just sit around and wait, but too short to context switch to another project. So what I end up doing instead are small hacks. And since I, once again, have decided to try and post stuff here more regularly, I decided to write a post about a hack I did yesterday.

A long time ago I tried to implement Python style generators in Scheme, and obviously I am a better programmer now since I was able to just sit down and write it.

As you can see the loop construct has the ability to break the loop and jump to the next iteration by explicitly naming the break and continue continuations. The fact that these have to be named explicitly is a good thing since it enables you to define different names for the break/continue continuations for different loops when you nest loops. You can of course name them break and continue, respectively, like so:

The generator definition also needs the yield continuation to be named explicitly. This is usually not what you want, so instead we define a convenience syntax for defining generators in the form of a &quote;unhygienic&quote; macro (this is the syntax used for unhygienic macros by most scheme implementations, PLT scheme that I used for example):

These macros was what prevented me from completing this when I first tried a few years ago. I started with them, and wanted them to be portable across all Scheme implementations, and unhygienic macros are not defined in the R5RS standard. What I should have done is what I did now, start with the interesting stuff and add just a little implementation dependent code for the final touches.

Now we can use these macros to define a range function that behaves in about the same way as the xrange function in Python:

To understand how the generators work let's look at what a generator definition and for-loop construct expands to:

; A generator expands to a function that accepts the defined parameters(lambda params; The generator function, when called, returns a function to which; the for-construct passes the break continuation and a closure; representing the body of the loop (lambda (done loop); yield is defined as a function that takes arbitrary parameters (define (exit . values); when invoked it retrieves the current continuation to be; able to resume then calls the loop body with the continuation; and the passed in parameters as arguments (call/cc (lambda (next) (apply loop (cons next values))))); The actual body of the generator is defined by the user,; this executes arbitrary code and calls yield. . body))

; The for-construct starts by retrieving the current conitunuation; this is used for breaking the loop(call/cc; It then calls the generator with two arguments:; the break continuation and the body of the loop. (lambda (breaker) (generator breaker; The body of the loop accepts two arguments:; the continue continuation and the argument that was passed to yield. (lambda (continuation variable); All it does is execute the body . body))))