Design Principles for Metaprogramming
December 19, 2012One of my favourite bits of programming philosophy is Alan Kay’s famous email about the big idea of object-oriented programming:
I’m sorry that I long ago coined the term “objects” for this topic because it gets many people to focus on the lesser idea.
The big idea is “messaging”. […] The Japanese have a small word - ma - for “that which is in between” - perhaps the nearest English equivalent is “interstitial”. The key in making great and growable systems is much more to design how its modules communicate rather than what their internal properties and behaviors should be.
Lately, I’ve been thinking a lot about this problem of “making great and growable systems.” In particular, I’ve been thinking about the tension between having an extensible language and runtime versus being able to statically reason about the code. In JavaScript, for example, it can be extremely difficult to predict the side effects of even the simplest piece of code. Java, on the other hand, makes certain kinds of magic impossible (or simply so painful it’s not worth it), leading to things like AbstractSingletonProxyFactoryBean.
But there are other options. In the same email, Alan also says:
It is vitally important not just to have a complete metasystem, but to have fences that help guard the crossing of metaboundaries.
I think this is a really important idea. Languages and runtimes with strong metaprogramming facilities, such as Lisp and Smalltalk, are extremely powerful. However, many programmers shy away from metaprogramming because it’s too “magical.” I think Alan’s point is that languages should make magic possible, while still allowing the programmer to reason about what kind of magic exists and where it is.
That’s why I really like how Dart implements reflection:
Reflection in Dart is based on the concept of mirrors, which are simply objects that reflect other objects. In a mirror-based API, whenever one wants to reflect on an entity, one must obtain a separate object called a mirror.
The basic idea of mirrors is to separate reflection from the core behaviour of objects, and put it into a separate module which can be removed or replaced. And, since Dart imports must be compile-time constants, it means that static analysis — and perhaps more importantly, a programmer reading the code — can determine if a piece of code uses reflection or not.
Probably the best introduction to mirrors is Gilad Bracha and David Ungar’s paper Mirrors: Design Principles for Meta-level Facilities of Object-Oriented Programming Languages. It goes it more detail about mirror-based APIs and the importance separating meta-level facilities of a language from the base functionality. It’s well worth a read if you’re interested in language design.