In my post about Elegant code and craftsmanship, I talked about how DRY – or Don’t Repeat Yourself – is one of the prime directives I try to follow as I develop.
In this series of posts, I’m going to post small tidbits of cool language constructs in various languages that help keep things DRY. They’ll all be tagged under language love, so you’ll be able to search and keep track of them in an easy way.
Today’s tidbit: generating methods in Ruby.
Sometimes you write a lot of code that looks like this:
def commit_all @productions.each do |k,v| k.commit end end def dump_all(d) @productions.each do |k,v| k.dump(d) end end def reset_all @productions.each do |k,v| k.reset end end
Doesn’t feel very DRY, does it? In fact, if you end up changing how you store your productions, you’ll have to change the code at least three times. Urgh.
Ruby allows you to define methods dynamically, on the fly. So you can instead write something like this:
[:reset, :commit, :dump].each do |m| define_method(m.to_s+"_all") do |*args| @productions.each do |k,v| v.send m, *args end end end
Note how the _all* methods also correctly forward the arguments, if any, to each production’s method.
Cool, no? Of course, this is really just classic metaprogramming much like Lisp. I just love how Ruby expresses it! The syntactic sugar is a nice mix between too verbose and too sparse, while still being flexible enough to allow lots of metaprogramming magic. (I will admit the way Ruby expresses variable arguments is a little confusing, though…)