关于MetaProgramming [DSL in Action节选]
These dynamic behaviors are governed by the metaobject protocol ( MOP) that each of these languages implements in their runtime. The metaobject protocol of a language defines the semantics of the extensibility of the language artifacts. Take a look at the accompanying callout for a gentle introduction to the concept of MOP in programming languages.
DEFINITION A meta-object is an abstraction that manipulates the behavior of other objects. In an OOP language, a metaclass might be responsible for creating and manipulating classes. To do that, the metaclass needs to store all information that’s relevant to the class, like type, interface, methods, and extension objects.
A meta-object protocol ( MOP) for a language defines the semantics of the extensibility of programs written in that language. The behavior of theprogram is determined by the MOP, including aspects of the program that can be extended by the programmer during compile time or runtime.
Metaprogramming is the ability to write programs that generate new programs or that change the behavior of existing programs.
In an OO language like Ruby or Groovy, metaprogramming implies capabilities that extend existing object models, add hooks to alter the behaviors of existing methods (or even classes), and synthesize new methods, properties, or modules during runtime through introspection.
Languages like Lisp use macros as the metaprogramming tool that let you syntactically extend the language during the compilation stage. Although the primary form of metaprogramming that’s supported by Groovy or Ruby is runtime, Lisp metaprogramming is compile time, and doesn’t incur any runtime overhead. (Both Groovy and Ruby have library support for compile-time metaprogramming through explicit manipulation of the AST s. But it’s nowhere near as elegant as Lisp.
Java also offers metaprogramming capabilities through annotation processing and aspect-oriented programming ( AOP); it also defines all its extensibility mechanisms through its MOP.
Statically typed languages like Haskell and OCaml that have traditionally relied on pure embedded semantics for designing DSL s now offer type-safe compile-time metaprogramming through extensions like Template Haskell and MetaOCaml respectively. For more information, see http://www.haskell.org/th/ and http://www.metaocaml.org/
Why is metaprogramming support such an important feature for a language to host a DSL ? The answer is that because metaprogramming support makes a language exten-sible, the DSL that you’ve implemented in an extensible language also becomes transi-tively extensible.
Compile-time metaprogramming lets you construct and manipulate pro-grams during compile time. You can define new constructs and interact with the compiler to perform syntactic transformations and application-specific optimizations. with compile-time metaprogramming, you can grow a language seamlessly toward your domain syntax.
To use the most common form of compile-time metaprogramming you implement syntactic macros.
Macro implementations vary in complexity and power, from the textual macros offered by C preprocessors to the sophisticated AST -based ones offered by variants of Lisp and some statically typed languages like Template Haskell and MetaOCaml.
Besides macros, some languages offer other preprocessor-based capabilities for compile-time metaprogramming, like the templates in C++, AOP, and annotation processing. Some languages like Groovy and Scala also have implementations of explicit compiler plugins, which provide some metaprogramming capabilities in the form of AST manipulation.
C++ offers templates as one of the most dominant protocols for metaprogramming. C++ templates support powerful code-generation mechanisms through manipulation of data structures during compile time. This form of compile-time metaprogramming has been used quite successfully in scientific and numerical applications for generating inline versions of algorithms that employ techniques like loop unrolling for optimizing performance.
LISP AND C LOJURE: MACROS
Lisp has the most sophisticated and complete support for compile-time metaprogramming, through its system of macros. Unlike C macros that have limited expressive power and that operate based on textual substitution, Lisp macros are powered by the full extensibility of the language.
JAVA : ANNOTATION PROCESSING AND AOP SUPPORT
Java also supports a limited form of compile-time metaprogramming through annotation processing and support for AOP . You can use annotations in a Java program that get processed during build-time. These annotations generate code that can supplement or alter existing program behavior.
AspectJ is the aspect-oriented extension to Java that offers a small set of powerful constructs that let you inject additional behavior into existing programs through bytecode instrumentation.
CST & AST
When you write a program in most languages, what you’re writing is being represented as the concrete syntax tree (CST ). The CST is the faithful representation of your program, including the white spaces, com-ments, and any metainformation you generate.
Your program is then passed through scanners, lexical analyzers, and parsers to generate what we call the abstract syntax tree (AST ). The AST is the syntactic essence of your program that gets pipelined into the next phases of the compilation process. It goes through the usual steps of transformation, optimization, and code genera-tion.
The parser of your language is primarily responsible for all such trans-formations that lead to the generation of the AST from the CST .
Code as data
In Lisp, every program is a list structure, which is the AST of the code itself. As a result, code is seen as having the same representation and syntax as data. By standardizing on this simple protocol, the language publishes the abstract syntax to the programmer. This abstract syntax is simple—it’s a list. Any metaprogram that you generate using Lisp needs to conform only to this simple, standard representation.
Data as code
By using the QUOTE special form, data syntax can be embedded easily into the code syntax. Lisp macros are good examples of this philosophy. In fact, Lisp extends this paradigm of data as code and offers a full-blown template mechanism for writing metaprograms. In Common Lisp, we call this quasiquotation . In Clojure, the same feature is implemented through syntax quote , unquote, and splicing unquote .
Kiczales, Gregor, Jim des Rivieres, and Daniel G. Bobrow. 1991. The Art of the Metaobject Protocol. The MIT Press