Saturday, March 12, 2005

Aspects vs. Macros

Both aspects and macros are a kind of metaprogramming that allow programmers to specify "hooks" in a program: "whenever you see X, insert the code Y." There is naturally some overlap in the way they are used. But there are key differences.

Aspects are added separately, on top of already-meaningful code. By contrast, since macro invocations are explicit and local, they are meaningless without their corresponding definitions. This difference is a consequence of Filman and Friedman's characterization of aspects as quantification + obliviousness: in AOP, programmers specify join points and advice separately from the code they are advising, which changes the behavior of the advised code without modifying the code itself. Macros, on the other hand, must explicitly ask for code to be inserted at their point of insertion.

It's easy for macrologists to claim the moral high ground: obliviousness is the controversial part of AOP. It violates abstraction boundaries and thwarts local reasoning. And I've now heard more than one researcher advocate a view of aspects as modular units of conjunctive partial specification. I'm game, but I'm also sympathetic to the view that if you go too far in this direction you don't have aspects anymore.

And sometimes, this lack of obliviousness leaves macros wanting. I'm not sure if the following example is truly illustrative, but it just came up and it's what got me thinking about all this. Consider a macro called define-struct-hierarchy, which allows a programmer to define a collection of record types and subtypes arranged in a hierarchy by expanding to corresponding define-struct invocations:
(define-struct-hierarchy
(parent (foo bar)
(child-1 (grunch))
(child-2 (frotz))))
=>
(define-struct parent (foo bar))
(define-struct (child-1 parent) (grunch))
(define-struct (child-2 parent) (frotz))
Now if we plan to document these types, we will almost certainly want to automate the document generation. (Trust me.) But how? The define-struct-hierarchy macro shouldn't be generating documentation every time we perform an expansion, and besides, that functionality is totally irrelevant to the main functionality of defining the datatypes. We'd like to be able to write a separate program that finds the declaration inside the program and generates the corresponding documentation. And we don't want to have to walk the entire program. We should be able to say, "for all macro invocations of the form (define-struct-hierarchy etc), generate the documentation." Otherwise, we end up performing painful contortions like putting the definitions in a separate file and textually including them in the program.

This is a simple and tiny example. Maybe obliviousness just doesn't scale. I sure don't want to go on record as an enemy of abstraction. But I'd also like to understand the middle ground.

No comments: