POSTS
Ottawa Metaprogramming Presentation
BlogLast 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:
Presentation to Ottawa RUG:
1. Introduction
1. Steven Harms, San Francisco, CA, US
1. @sgharms
1. http://stevengharms.com
1. gh: sgharms
1. Employee: shopittome.com
1. Presentation
1. Intro::"What Do We Mean by Metaprogramming" or, Astounding Beginners
1. `method_missing`
1. Instance methods
1. Alternate definition of class methods, methods
1. Programming is the art of passing messages (10 mins)
1. https://speakerdeck.com/sgharms/practical-metaprogramming-ruby
1. Standard model
1. Metaprogramming
1. Standard tools ( 10 mins) and progressing up the ladder
1. Advanced usage case study: a DSL (10 mins)
1. https://github.com/sgharms/VerbvectorGenerator/blob/master/lib/verbvector.rb
1. https://github.com/sgharms/LatinVerb/blob/master/lib/linguistics/latin/verb/latinverb/verbvector_description.rb
1. From the trenches (10 mins):
1. Metaprogramming in Rails
1. Do's and Don'ts: Thinking about metaprogramming
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 hiss
or slither
or 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.
class Rattlesnake
def initialize(species)
@species = species
end
def method_missing(sym, *args)
puts "dis #{args.empty? ? '' : args.join(', ')+' ' }#{@species} snake will #{sym.to_s}."
strike_method = [@species, 'strike'].join('_').to_sym
send(strike_method) if self.respond_to? strike_method and sym.equal?(:strike)
end
def cottonmouth_strike
puts 'a cottonmouth strike is deadly'
end
end
thing_i_saw_while_camping = Rattlesnake.new 'cottonmouth'
thing_i_saw_while_camping.hiss
thing_i_saw_while_camping.slither
thing_i_saw_while_camping.strike 'dangerous', 'vituperative'
Output:
dis cottonmouth snake will hiss.
dis cottonmouth snake will slither.
dis dangerous, vituperative cottonmouth snake will strike.
a cottonmouth strike is deadly
Instance methods
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.
class Rattlesnake
def initialize(species, color='unknown')
@species = species
@color = color
end
def shed
puts "in a mystical transformation, the #{@color} #{@species} snake sheds"
end
end
texan_snake = Rattlesnake.new("cottonmouth", 'brown')
texan_snake.shed
coily = Rattlesnake.new("video-game", 'purple')
coily.shed
coily.instance_eval do
def curse
'@#$!!?!'
end
end
puts "Coily can curse #{coily.curse}"
puts "A regular old snake #{texan_snake.respond_to?( :curse ) ? 'does ' : 'does not '} know how to curse."
Rattlesnake.class_eval do
def curse
'avadra kedavera!'
end
end
puts texan_snake.curse
puts coily.curse
Output:
in a mystical transformation, the brown cottonmouth snake sheds
in a mystical transformation, the purple video-game snake sheds
Coily can curse @#$!!?!
A regular old snake does not know how to curse.
avadra kedavera!
@#$!!?!
Notes
Here all Rattlesnake
s 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 curse
. Now 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
class Ursine
class << self
def bare
puts 'no one likes a bare bear'
end
end
def self.constellation
puts 'ursa major'
end
define_method :raid_bear_box do
puts 'wants a pick-a-nick basket'
end
end
Ursine.constellation
Ursine.bare
yogi = Ursine.new
yogi.raid_bear_box
Output:
ursa major
no one likes a bare bear
wants a pick-a-nick basket
Notes:
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.
http://speakerdeck.com/sgharms/practical-metaprogramming-ruby
In that presentation I lead people up a series of steps that should help them become comfortable with rolling metaprogramming into their codebase.
DSLs
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
and get
@aVerb.active_voice_indicative_mood_present_tense_first_person_singular_number
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:
@aVerb.active_voice_indicative_mood_present_tense_singular_number_first_person
160 + a lot more methods to describe
And what if wanted to leave off some specifier and get a more general subset?
@aVerb.active_voice_indicative_mood_present_tense_singular_number
or
@aVerb.active_voice_indicative_mood_present_tense
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.
Conclusion
Thereafter I gave a few general comments about responsible metaprogramming:
- Always strive for clarity for other programmers to read