Saturday, 18 December 2010

Revisting replacing a Ruby instance method with a closure

Last month I looked at how to replace a Ruby method of a single object instance with a closure, before defining a module that could make this easier. Since then I've learnt another option which I thought I'd share as it helped me get a greater appreciation for Ruby modules.

Note: I am a Ruby n00bie, so take all this with a suitable amount of salt. If this (or the previous posts) violate Ruby conventions, or there is an idiomatic way of solving this, please let me know.

Quick recap

The problem is fully described in the original post, but it basically starts with this class:

class Greeter
    def say_hello
        puts "Hello World!"
    end
end
greeter = Greeter.new
greeter.say_hello
#=> Hello World!

I then wanted to replace say_hello on that single greeter instance with a method that would close over a local variable, like this:

name = "Anonymous Dave"
# replace say_hello on greeter so it puts "G'day #{name}"

greeter.say_hello
#=> G'day Anonymous Dave

name = "Clarence"
greeter.say_hello
#=> G'day Clarence

Standard reopening of the instance (or even the Greeter class) and redefining the method won't work here, all because we have the pesky requirement of closing over our local name variable, which means we need to use a block (basically a lambda function for C# people). We can use Class.send to call the private define_method which takes a block, but that will add it to every instance of Greeter, not a single instance.

Modules to the rescue

We solved this in the original post by referencing the instance's metaclass (aka eigenclass), but there is another way:

name = "Anonymous Dave"
new_say_hello = Module.new do
    self.send(:define_method, :say_hello) do
       puts "G'day #{name}"
    end
end

Here we've created a new anonymous module that sends define_method to create a say_hello method using a block, in the same way as we could have reopened the Greeter class and added it to every instance. The difference here is that this module has not been mixed in anywhere yet; we can choose exactly where we want to apply it. In this case, to our single instance:

# Mixin module to greeter instance to add our new say_hello method
greeter.extend new_say_hello

greeter.say_hello
#=> G'day Anonymous Dave

name = "Clarence"
greeter.say_hello
#=> G'day Clarence

# Other instances are unaffected by this:
another_greeter = Greeter.new
another_greeter.say_hello
#=> Hello World!

I think I still prefer the Meta module approach, but this way has the advantage of sticking closely to standard Ruby constructs and manages to avoid metaclasses.

What was most helpful to me out of this as a Ruby n00bie is the understanding that we can work using class scope within a module (avoiding metaclass shenanigans), then apply that scope selectively by including the module in a class, or by extending an instance with the module.

Four years

After much hesitation, and against my better judgement, I have decided to uphold my yearly tradition of marking my blogoversary. Last week was the end of my 4th year of blogging and, as is my custom, I will proceed to indulge in some grandiloquent pomposity (I had to look up what that means. If this post gives you nothing but that awesome phrase then my work here is done :)). Without further ado, here's my yearly recap:

  • Published 28 blog posts (excluding this one). Wrote several more. This brings my grand total to 328 (at least 3 of which are worth reading :)).
  • My most visited post written this year was this introductory MVVM post, while my most visited overall was last year's post on SOLID principles. My favourite from this year was probably What exactly is TDD driving?, or maybe Refactor or redesign?.
  • I inadvertently became anonymous thanks to the efforts of Anthony and Richard.
  • Browser traffic breakdown (with change from last year): Firefox 40% (-4%), IE 29% (-8%), Chrome 25% (+17%)
  • Anthony and I released NSubstitute. According to Ohloh it's 10,000 lines of C#, and 21 person years of work worth $1.2 million (which just goes to show how far the US dollar has fallen).
  • Switched my blog post editor from Notepad++ to GVIM.
  • I, or someone that sounded quite like me, was on the Talking Shop podcast yakking about NSubstitute with my bearded project co-owner.
  • The percentage of traffic to my blog from Bing rose from 0.7% to 1.6%.
  • I gave my first (and probably last) usergroup presentation. Thanks to the good, tolerant people at Sydney ALT.NET for having me.
  • One of the more ambitious search terms used to find my blog this year was "implementing prolog in f#". The most depressing? Probably "microsoft.sharepoint.workflowactions.sendemail spgroup".
  • I got to go on Udi Dahan's Distributed System Design course, attended the YOW! conference in Brisbane as well as Eric Evans' DDD Workshop, and Corey Haines' workshop on Practice. Also went to DDD Sydney and a Microsoft WebCamp in Sydney.
  • I got to meet loads of awesome people I only previously knew online.

I hope you've found something here over the last 4 years that has helped you out. Thanks for reading, and all the best to you and yours for 2011 and beyond. :)

Best regards,
Some bloke called Dave