Tuesday, December 23, 2008

Hard Stuff in Ruby- Part One

The title is a little misleading here. This "Stuff" is not necessarily hard. It is necessarily in Ruby, but could very well be in other programming languages, although perhaps not as pervasively. I have a running list of these things that I feel like I should understand better about Ruby. I'm not claiming expertise on any of the topics. I'm just taking a minute to get to know them better.

Here's the list:

-Metaprogramming
-Blocks
-Lambdas
-Singleton
-Method Missing
-eval methods
-bindings


I'll start with metaprogramming. The big idea here is that you can modify classes on the fly. Might not sound too exciting at first... But here are two simple ways that you can use it to great effect.

Let's say that you want to work with time. This can be a little tricky because you have to work with ints, maybe floats, and Time objects. Let's say you get the current time like so

Time.now

Great. Now you want to add a second to it.

Time.now + 1

Okay... How about 1 minute?

Time.now + 60

Already things are starting to get ugly... but let's go one step further. Add 3 hours 24 minutes and 32 seconds

Time.now + 3 * 60 * 60 + 24 * 60 + 32

Okay... so that works... it's a little ugly though. Metaprogramming to the rescue:

class Fixnum
def minute
self * 60
end
def minutes
self * 60
end
def hour
self * 60 * 60
end
def hours
self * 60 * 60
end
def second
self
end
def seconds
self
end
end

Time.now + 3.hours + 24.minutes + 32.seconds

Much more readable and easier to work with. I don't know if the "seconds" method should be there, given that it's not doing anything really. But it seems better to keep it consistent. Honestly, I'd rather add 32.seconds to a Time object than 32. So extending the Fixnum class is handy anytime you need a DSL for something involving numbers. Opens a lot of interesting possibilities: Conversion between base systems and music DSL's come to mind.

Second up is something that I haven't used before, but it looked like good voodoo, and I know a couple of places where it will come in real handy, real soon.

Let's say that you have an object that might get instantiated and might not. This, like the Fixnum thing, is another issue that I've run across in Tasker. The problem here is that I don't think that I should have to define the weights and times for my tasks. They should default to something. I'm reading in from a yaml file, so the task info is stored in a multidimensional hash.

I ended up writing this code to avoid the nil time:

if hash[key] and hash[key]['time'] then
time = hash[key]['time'].to_i.hours
else
time = 1.hour
end


But here's what I'll change it to very shortly.

def nil.[]; 1; end
time = hash[key]['time'].to_i.hours


Much cleaner and smarter. So that's all there is to... nope. Actually, there's a lot of interplay between the hard stuff topics, so the next hard stuff section will be Metaprogramming + X. After that is Metaprogramming + X + Y.


A couple of notes:

Other examples of simple metaprogramming are the attr: decorators found all over the place in ruby. You see them a lot in rails's yourmodel.rb files.

For more on metaprogramming, I would recommend this. My nil.[] method borrowed heavily from his nil.name method.

No comments:

Post a Comment