Came across an article on Windows Dev Network site. The site requires a login, so just copying the content here. The author is part of the team that develops AspectJ. Again, I noticed that he talks of attributes and then will lead into AOP... so they are quite interconnected.
A Little Goes a Long Way
Attributes in C# and annotations in Java provide a hook for principled macros and metaprogramming in the C family. Lisp has had this technology for years, offering many lessons.
By Gregor Kiczales
C# attributes and annotations in Java’s upcoming JDK 1.5—both hereafter called tags—provide the basic hook for adopting a range of useful meta-programming techniques. Tags are new to these C-syntax languages, and we’re starting to see their adoption by programmers, tools and APIs. Lisp has had technology like this for years, so it’s worth looking back to see what 30-plus years of experience can teach us.
The Lisp family of languages has always had the upper hand on macros. Lisp’s macro technology is powerful and principled; thus, Lisp users have developed a rich practice of when and how to use macros to good advantage.
Reading this, you may be thinking “Macros—aargh! Macros are terrible!” C’s text-based macros are so fragile that for many people, the term has developed very negative connotations.
The first lesson is that in Lisp, the term macros has never meant what it does in C. Lisp macros are syntactic: They operate on the program abstract syntax tree (AST) after parsing. This gives the macros a higher—and more principled—level from which to operate. By their nature, syntactic macros produce well-formed programs, which makes them inherently easier to understand and debug than text-based macros.
C# attributes and Java annotations are not themselves a macro facility, but because they’re parsable, they inherently support syntactic macros: They have already adopted Lisp’s first lesson.
After years of experience with syntactic macros, a further enhancement called hygienic macros emerged. Hygienic macros gave macro writers tools that automatically dealt with such issues as preventing name capture, importing definitions from other packages and the like. These issues are also critically important for C# and Java, and should find their way into the macro- and bytecode-editing toolkits.
Two Positions for Tags
The tags that support macros can potentially appear in evaluated and nonevaluated positions. An evaluated position means that the tag appears somewhere you’d expect it to be evaluated (compiled first, of course). A non-evaluated position means that, by default, you’d expect the annotation to be just an attribute of the declaration.
In Lisp, almost every position is evaluated, so, for a number of years, Lisp macros used only evaluated-position tags. But in the 1980s, the declare syntax was added, providing a nonevaluated position for tags.
C# attributes and Java annotations can appear in a number of positions (the two mechanisms differ slightly on this), but for now, just consider that they can appear before any declaration.
“Macro and Attribute Syntax” compares tag appearance in Lisp and C#, listing three Lisp implementations of a procedure that updates a record, along with two C# implementations. All five examples wrap the core update functionality in a transaction. In the left column, the transaction code appears explicitly in the procedure. In the top center, in Lisp, an evaluated-position macro is used to wrap the transaction code. In the third column, an annotation declares that the body should be in a transaction. The corresponding Java code looks very similar to the C#, but with a tag syntax of @Transactional.
In Lisp, the declare tag was commonly used for simple metadata tagging; say, telling the editor how to indent code, or to provide different kinds of documentation. Similarly, in C# and Java, attributes have been used at their basic level for metadata that simply labels or decorates a program; typical examples include deployment descriptions, documentation and cross-referencing code with UML tools. These tags can also be used to name anonymous procedures or classes—in Lisp, the named-lambda macro was used for this. (Next month, we’ll see a powerful synergy between this use of tags and AOP.)
Simple Semantic Extensions
By default, tags have no semantic effect—in this way, they differ from Lisp macros—but this doesn’t mean that attributes can’t have semantic effect. A preprocessor can read the source code and make additions or changes as directed by the attributes. Alternatively, the compiler can preserve the tags, and then a postprocessor can read the bytecode and manipulate it as directed by the tags. Both of these have the essential properties of syntactic macros.
A simple example of a semantic extension is a tag that says something like “Generate getter and setter methods for a field,” or “Generate value-checking code for a field or parameter” (such as [Positive] to ensure an integer is positive). There are already products that provide this kind of functionality for C# and Visual Studio, and doubtless Java will soon see similar tools.
Beware Macro Overload
Lisp history also teaches us the important lesson that a little goes a long way when using semantic extensions—in the Lisp world, it was common for macro tyros to define a slew of them. It was great fun—it made their code shorter, and, to them, clearer. I was as guilty of this as anyone; for about three months, I tormented colleagues with my extreme use of macrolet, which allowed a kind of nested macro.
The problem with an abundance of semantic extensions is exactly the same as the feature—it leaves lots of little languages floating around. Lisp’s history in this respect has taught us that people found it unduly difficult to read each other’s code: Imagine reading C# code with lists of attributes longer than the method bodies, or just with as many different attributes as the underlying API.
New languages should be defined sparingly, only when they make a big difference to code clarity, and when it’s likely that any future reader of the code will easily understand it. Extensions to define getters and setters are probably reasonable. Extensions to deal with a focused and well-defined element of domain knowledge may be reasonable. Widespread use of semantic extensions for things that look almost as good when done with ordinary OOP (or AOP) is probably not.
So have fun with attributes and the metaprogramming tools they enable. Used properly, they can help with what I’ve always thought was our ultimate goal: making the code look more like the design the programmer’s thinking about. But remember, a little goes a long way.
Next month: Now that I’ve explained attributes and annotations, next month’s column will focus on using them together with aspects.
Gregor Kiczales led the Xerox PARC teams that developed aspect-oriented programming and AspectJ, and is a leading AOP evangelist based in Vancouver.