We've been working on many things:
- Optimizing generic word dispatch (Slava)
- Updating the Solaris x86 port (Slava)
- Windows IPv6 and UDP (Doug)
- Updating the math library for the new module system (Doug)
- Optimizing the boids demo (Eduardo)
- Macros (Slava and Doug)
The macro feature is pretty cool. Factor has had compiler transforms for a while, but macros make them easier to use. For example, if you want to write a word which drops n elements from the stack, a first approximation might be:
: ndrop ( n -- ) [ drop ] times ;
However, this is inefficient since the compiler is unable to infer the stack effect, and in any case, if n is constant we would like to unroll the loop and not have to do it at run time. So we can use a compiler transform:
: [ndrop] ( n -- quot ) \ drop <repetition> >quotation ;
: ndrop [ndrop] call ;
\ ndrop 1 [ [ndrop] ] define-transform
[ndrop]word outputs a quotation which drops the top n stack elements; this quotation does not loop, it is just
droprepeated a number of times. The definition
[ndrop] callfor the
ndropword is only used in the interpreter or when the value of n is not known at compile time. The compiler transform is invoked when the value is known at compile time, and it outputs a quotation to replace the word with. So in this example,
4 ndropcompiles to
drop drop drop drop, which is optimized to a single instruction which decrements the stack pointer. No looping at all.
However, there is a bit of boilerplate in the above code which becomes apparent in more complex examples. With macros, you can now write:
MACRO: ndrop ( n -- ) \ drop <repetition> >quotation ;
So a macro receives its inputs and outputs a quotation -- the compiler invokes macros at compile time and replaces the macro call with the generated quotation, and the interpreter generates a quotation by calling the macro every time.
The cool thing is that the
MACRO:word can be defined without modifying the compiler or anything; it is just a simple wrapper around
define-transform. Check it out. The same file also defines some short-circuiting boolean operators as macros.
A more involved example of macrology can be found in extra/shuffle - this file defines some hardcore shuffle words (duplicating, dropping, swapping arbitrary numbers of stack elements) and the implementation in terms of macros looks nicer than the implementation using compiler transforms directly.
In fact these macros are exactly like Common Lisp macros -- but whereas people say Common Lisp macros are a key, fundamental language feature which differentiates Lisp from other languages, in Factor we're able to add macros as a library (and it only takes 5 lines). Another "fundamental" Lisp feature, lexically scoped closures, is also just a Factor library -- and it compiles to efficient code to boot, without the compiler having to know anything about lexical scope! I really think Factor is a better Lisp than Lisp -- it pulls off the extensible language tricks just as well or better, and the implementation is simpler and more transparent to boot.
Factor 0.90 is almost done -- I've finished most of the remaining loose ends. Updating the Solaris port was the last big item. This didn't take long because Doug had a Parallels virtual machine with Solaris installed and ready to go.
I'm still in Austin for a few more days. Going to go bike riding, jetskiing hopefully, and I also need to buy a cowboy hat! We're also going to meet up with the RScheme guy. Eduardo knows him from way back in the day; before joining the Factor community, Eduardo was a hard-core Schemer. He wrote a window manager in Scheme which was the precursor to Factory, and he also wrote a DNS server; I've been lobbying for him to port this DNS server over to Factor too. Now that we have UDP support on all platforms, it would be a great stress-test for the network backends!