Last Tuesday I had the privilege of speaking to the Ottawa Ruby user’s group via remote teleconference. While I would have preferred to be there in person so that I could interact with the attendees (despite the -23C temperature!), I really enjoyed having the chance to speak to these fine folks.
Presentation to Ottawa RUG:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
What Do we Mean by Metaprogramming Code Samples
Metaprogramming is definitely one of the coolest features of Ruby. I’m a
pretty “meta-” thinker, so when I saw Ruby’s capabilities around
method_missing and instance variable addition, I was completely hooked. I
hoped, by this section to expose the total “nubies” to some of that same magic.
Method Missing Example
One of the fascinating features of Ruby is the ability to not define some method when writing out the class, but rather let the “I don’t know what method that is” error get trapped by
method_missing and then, based on some logic, do something other than throw the error. Here we don’t define the methods
strike. Rather we intercept the errors and then creatively handle them. I have no idea why I got on the topic of snakes when writing these examples.
Bonus: Show off that Ruby will let you drop parentheses. Protip: If you care about what comes back (like calling a function) use parents (as I learned from Avdi Grimm); else, feel free to leave them off.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
1 2 3 4
Most programmers familiar with OO will recognize the general pattern that you make a cookie-cutter (a class) and then use it to stamp out instances of that cookies (instances). Methods are defined on the class and all the instances have the ability to call those methods.
The amazing thing about Ruby is that it will let you put methods on an instance. It stuffs those methods in the singleton class (or eigenclass or metaclass). This is where classes stuff their class methods too. When you understand how this works the whole system seems beautiful and elegant and entirely obvious. When you’re new, it seems like magic. Here’s a demonstration of the magic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
1 2 3 4 5 6
Rattlesnakes know how to shed. The method is applied on the class, no surprise. We create a specific rattler named coily (after Q-bert). On coily we add
curse. Then we demonstrate that
coily can curse but
texan_snake cannot. Then we put a method on the class so that all snakes know how to
texan_snake can curse and
coily, who could already curse, still has his unique
curse method because its definition (on the instance) takes precedence over the definition on the class. This is pretty amazing stuff.
Alternate class method definitions; the mystical eigenclass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 2 3
Standard Ruby pedagogy will teach you about classes which can support methods that the instances will inherit. Also you will be instructed that sometimes you want to put a method on the class itself not on instances. A classic example is something like
Math.sin. The sine function will never be instantiated, but it makes sense that its terminology live within a namespace (like Math). The classic way of writing this method would be
def Math.sin or, while inside the Math class definition
self.sin (which is clearly the same thing). The invocation that got my curiosity up was the
class << self block: what’s going on there? It turns out that you’re opening up the singleton_class, the eigenclass.
These were the preliminary exposures to metaprogramming that lead me to read Paolo Perrotta’s book on the topic and to undertake my Rubyconf 2011 presentation.
In that presentation I lead people up a series of steps that should help them become comfortable with rolling metaprogramming into their codebase.
The group asked about DSL construction in Ruby. I will provide a bit of context here as to why I did this. I received some feedback via Twitter on this topic and I would like to give a bit more context.
It started simply, I wanted to be able to say in an IRB like shell
@aVerb.active(TAB)indic(TAB)pres(TAB)fir(TAB)sing(TAB) and get
So, for love of TAB completion, I wrote a DSL.
As it turns out, I needed to generate something like 160 unique “vectors” that every class needed to know about so that I could TAB-complete. And what if I wanted to have the order be flexible:
160 + a lot more methods to describe
And what if wanted to leave off some specifier and get a more general subset?
160 + tons of methods to describe.
So I needed something to perform the combinatorics to generate all those vectors and to apply them onto the instance so that I could TAB-complete. So I decided to write a verb-describing DSL. The following two links show my DSL and how it is implemented. In the presentation I suggest that DSLs are best for narrow-use cases. The most easily understood DSL is Ruby programmer-to-programmer, but sometimes it is very helpful to be able to write a DSL so that business-function specific clients can influence the code without knowing Ruby. I think Rails, Cucumber and RSpec have done a pretty good job at demonstrating how well DSLs can work.
Thereafter I gave a few general comments about responsible metaprogramming:
- Always strive for clarity for other programmers to read
- Metaprogramming is not a substitute for good OO