The following is an excerpt from Much Ado About Naught.
I have a confession to make. I have a guilty pleasure, and its name is metaprogramming.
I think every hacker has a type of programming they like to do just for fun. My fellow Ruby Rogue Katrina Owen talks about “therapeutic refactoring”, refactoring just for the sheer pleasure of it. Other folks enjoy performance tweaking, or code golf, or writing deliberatelyobfuscated code.
For me, it’s hacking on the language itself to support new semantics or syntax. Ideally, metaprogramming builds up the language to more closely model the problem domain. But sometimes, it’s simply about seeing just how far I can push the language.
Metaprogramming gets a lot of attention in the Ruby community. I think that more than anything else this is because of how accessible Ruby makes metaprogramming compared to other languages.
Metaprogramming is possible in many languages, of course. In C, it’s mainly the domain of preprocessor macros. C++ added the potential for “template metaprogramming”, a static form of metaprogramming where complex quasi-programmatic generic type declarations are evaluated and reduced entirely within the compiler before a line of code is ever executed. This makes for a meta-language which bears little resemblance to ordinary C++ code, or indeed to any form of language outside the madness-inducing inscriptions carved into the temple walls of Cthulhus’s sunken city of R’lyeh.
Both C# and Java make provisions for metaprogramming. Unlike C++, they both enable dynamic metaprogramming, where code or classes are generated at runtime, using “ordinary” C# or Java code. They accomplish this through the use of reflection and elaborate meta-models—classes which themselves represent other coding constructs. The result is a verbose form of metaprogramming that resembles legal-ese: the class being generated, hereafter referred to as “the class”, shall have a method, hereafter referred to as “the method”, whose return value shall be of the type known as “Integer”, and whose arguments shall consist of the following…
The Lisp family of languages are renowned for their meta-programmability. And rightfully so. It is largely thanks to Lisp that other languages owe the concept of “building up a language to match the problem domain”. Lisp dialects have always emphasized the ability to grow new kinds of declarations and even control constructs in order to best suit the task at hand.
But that power comes at a price. The tractability of Lisp for metaprogramming purposes is the direct result of a fundamental compromise: Lisp agrees to make macros and other kinds of metaprogramming easy, so long as Lisp programmers agree to write all their programs in the form of S-expressions, so that the visual representation of a program has a one-to-one correspondence to the semantic structure of the program. This is called “homoiconicity”, if you want to be snooty about it.
In effect, the programmer agrees to forego the syntax sugar other languages offer in return for greater flexibility. It’s a bit like going to a hobby shop and buying a model kit which advertises the remarkable ability to build any model whatsoever—a jet fighter, a speedboat, a sports care, you name it. Inside the box is a big pile of generic plastic interlocking bricks. You can build anything you want, so long as you want something built out of rectangular plastic bricks with bumps on top.
Programmers in more “pure” functional languages such as Haskell also enjoy the ability to metaprogram, and meta-meta-program, and so forth. Unfortunately before they can do so they are legally obligated to write a 16-page essay on why a Monad is like a writing desk. So few ever get around to it.
And then there’s Ruby. Ruby’s metaprogramming is entirely dynamic; there are no macros or templates to be evaluated at compile time or before. But unlike C# or Java, there also is no reflection framework. Everything you need to take apart a class and reassemble it is exposed right there on the class itself, alongside your domain-level terms. Want to know how many
apple_tree object has? Send it the
#apple_count message. Want to what other messages it can respond to? Send it the
#methods message. There is no need for complex reflection invocations, like
Reflection.for_class(apple_tree.class). Everything, the meta and the not-so-meta, is equally discoverable and accessible at the ground level. Ruby takes a punk rock approach to metaprogramming: rather than sending its jeans out to the tailor to be mended, it sews patches on while it is still wearing them. Occasionally it accidentally sews a patch to its own leg in the process.
It is this accessibility that, for me, makes metaprogramming in Ruby so much fun. More than most other languages, metaprogramming Ruby feels like, well… programming Ruby.
Of course, the accessibility of metaprogramming tools in Ruby can be a double-edged sword. It’s all too easy to write code that breaks fundamental assumptions about how the language should behave, or that is just plain incomprehensible. When metaprogramming, it’s important to keep the “Spider-Man principle” in mind: with great power, comes great gobs of sticky web gunk clogging up the drain. Or something like that; I was never much of a comic book reader.
Literately metaprogramming a Null Object library
The text you’re now reading came out of my love of metaprogramming. It also stems, indirectly, from my previous book “Confident Ruby”. In that book I wrote about the Null Object pattern as one way to cut down on
nil-testing conditionals in Ruby code. Over the years I’ve given a lot of thought to how to best implement the Null Object pattern in Ruby, and as I wrapped up that book I thought it would be fun and perhaps instructive to write a library that made it easy to play with variations on the Null Object idea. I also thought it sounded like a great excuse to demonstrate some of my favorite metaprogramming techniques.
I wanted to capture the thinking behind the various features I added, as well as the “why” of my implementation decisions. So I decided to write the first release of the library as a literate program. That program is what you’re reading now. (It has since graduated from its literate-programming roots. The latest code can now be found at http://github.com/avdi/naught.)
I used Org-Mode in Emacs as my literate programming tool of choice. My basic process for writing the library was as follows:
- Explain the next feature I planned on adding, as well as the rationale for adding it.
- Write a set of tests which, to the best of my ability, echoed the explanation of the feature in executable form.
- Implement code to make the tests pass, writing up my reasoning for each choice as I made it.
- Also document each refactoring as I performed it.
The end result is a document about building a library which also functions as the literal source code for that library. I guess that makes it a fitting mirror for the subject matter: a program which is also a book about itself, concerned with the topic of code that reflects on itself and generates new code.
If that last sentence didn’t hurt your brain too much, read on. I hope you have as much fun reading as I did writing both the code and the prose. And hey, maybe you’ll learn a thing or two along the way.
Like what you’ve read so far?