"and evanrmurphy for getting me thinking about fexprs[2]"
I've been coming to the conclusion that although macros are great, they have some pretty severe limitations, due to them always being expanded at compile-time. So I'd be very interested to see if fexprs would be a good fit for Arc.
What I think would be neat is if macros were expanded at compile-time as much as possible, and when they can't be expanded at compile-time, they'll revert back to interpreting at run-time. Then, you can get full macro speed most of the time, but it would still allow you to do things like, say, use `apply` on a macro, or pass it as an argument to `map`, etc.
Alternatively, you could just not have macros, only having fexprs, and then try to optimize fexprs in various ways. I doubt it'd be as fast as expanding macros at compile-time, but... who knows, it might still be Fast Enough(tm).
Anyways, I think it's good for us to experiment with fexprs more, and see how things turn out.
---
In fact, because of the severe problems with macros, I independently came up with a pattern that rocketnia had already discovered: thunks. By wrapping every argument to a function in a thunk, you basically turn them into ghetto fexprs. So rather than doing this:
(foo a b c)
You'd do this:
(foo (fn () a) (fn () b) (fn () c))
But obviously that's a total pain, so what I did was rename "foo" to "foofn", and then have a macro called "foo". So that way, this:
(foo a b c)
Would expand into this:
(foofn (fn () a) (fn () b) (fn () c))
As near as I can tell, this is pretty much hygienic macros, since foofn is an ordinary function that has lexical scope. It's not nearly as clean or expressive as fexprs, but it's a cool little hack that can help work around the limitations of macros.
But, if we get to the point where a significant number of macros are basically just expanding into thunks... then I think we'd be better off having fexprs.
About thunks; this is actually a situation where statically typed languages have the potential to do something interesting that is hard in dynamically typed systems.
If foo takes an argument that should be a zero arity function that returns a number, and you write:
(foo (+ x y))
The compiler could recognize that the expression you used for the argument can be converted into a function with the correct signature. I think I have heard of languages on the JVM that do this, not sure which.
Also note that Smalltalk-80 is very much designed around the principle of thunks. E.g: