I'm not following. Why does "ModelClass.register_expiration_callback(keyname)" have to be in a macro expansion, rather than just the first statement of the cache() method? It doesn't appear to use local scope at all. Can you flesh out the example a bit more?
FWIW, I believe that Ruby or (especially) Smalltalk-style syntax for method calls and closures covers such a large set of the things macros are used for that their complexity cost (esp when building tools) outweighs their benefit.
Sorry, re-reading that it wasn't totally clear. The ModelClass.register_expiration_callback(keyname) has to be run at parse time, because there's no guarantee that the cache statement will ever be executed before a ModelClass instance is updated. You can't run things at parse time in Ruby (without exotic extensions); that's the whole point of macros.
I completely agree that there are a large number of uses for macros that "SmallTalk style" syntax completely replace. It turns out that a lot of things don't really require you to run code at parse time. Take anaphoric-if for example, which is classically implemented as a macro; all you really need is the ability to manipulate the local variables of a closure from the outside. Parse-time execution is not required.
With macros, every call to cache() can be expanded into:
Without macros, there's no way to catch deeply nested cache calls. I fake this by scanning the source for calls to cache() on startup right now.