Wednesday, August 22, 2007

FactorCon summer 2007

I'm in Austin right now with my girlfriend - we're staying at Doug Coleman's house. We've been eating mexican food, swimming, and having lots of fun in general. Doug, Eduardo and I have also been hacking up a storm. I've met Doug before when he came to Ottawa, but this is my first time hanging out with Eduardo. He's a really chill guy, sleeps during the day and then stays up hacking until 4 in the morning -- if you ever meet him, ask him to make you some vegan breakfast tacos, they're good at any time of day or night. It is really good to meet hackers face-to-face and write code together -- when one of us gets stuck, the others can help out, and the round-trip time is much shorter than if we were on IRC. I also got a chance to meet Ryan Murphy, who contributed a couple of Factor modules (priority heaps and EditPadPro support); he also lives in Austin.

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

The [ndrop] word outputs a quotation which drops the top n stack elements; this quotation does not loop, it is just drop repeated a number of times. The definition [ndrop] call for the ndrop word 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 ndrop compiles 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:
USE: macros

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!


george said...

Factor *is* the most powerful language.

Factor The most powerful language ever designed by man.

Email Paul Graham and say I made a language more powerful than lisp it is called factor.

Anonymous said...

We want pictures!

Daniel Ehrenberg said...

Hold on a second. As far as power goes, Lisp and Factor are still no more and no less than Turing-complete. Maybe Factor is a better Lisp than Common Lisp (certainly less crufty and more interesting than me) but I've never heard of a case where Lisp macros failed and Factor parsing words succeed.

In their current implementation, Factor "macros" are 80% compiler builtin, 20% this, as far as I can tell. (It would be possible to implement these macros as parsing words, but without making them extremely complex, they'd be a lot less flexible, and they'd mess up homoiconic syntax.) Still, they're a really cool achievement, and will make certain things easier to write.

For a wildly different perspective on macros being not builtin but derived from something more general, see Liskell (, where whole-program transformations are applied, and one of these whole-program transformations could implement macros.

Anyway, Paul Graham wouldn't like Factor because of our annoying convention to write out words ratr tn abr thm so nne cn ustd us.