I realised what it was a few days ago after reading Steve Yegge's rather over-dramatic criticism of Java (and other similar languages like C#) in his Execution in the Kingdom of Nouns post. But when you make it through all the theatrics, he does have a point. We often place pieces of logic in a class with no state - anywhere you use a Strategy pattern would be a good example of this. This was something I took for granted since I've never worked in a language where functions are first-class objects, so I believe he has a point - the only reason we put pure logic into a class with no state (in Java or C#) is because we simply have nowhere else to put it. So I did some digging around and found this marvelous post on closures and higher-order functions which helped me understand what the term 'functions as first class objects' really meant.
The thing which struck me after reading this article was that at some level I'd always thought of
method_object.call(*args)as reflection as I would do in Java or C# - and if you need to use reflection to get your job done, you've probably messed something up. I mean, yes, there are some situations where reflection makes complete sense, but I've seen cases where people try to bypass poor object design by using reflection. Besides, there is some a loss of performance in those languages when one uses reflection to invoke a method.
But in Ruby (as I'm given to understand at the moment :-)) this isn't the same thing at all. Here, it's perfectly natural way of doing what you would in Java or C# using a strategy. You simply no longer need to put your code in class because you can create a proc object out of it instead.
I'm sure a lot of other interesting ways of using proc objects will surface once I get down to it and do some solid reading. But first I have a few questions around this implementation of the strategy pattern using procs rather than classes.
- Where would I place my proc object with the intent of avoiding name collisions? In a module, perhaps?
- How do I unit test this, either using Test::Unit or any other alternative?
- Have I gone and gotten it all wrong and missed the point completely - again?
When I can find the time, I'll play with some Ruby code and see what happens, but if you have any suggestions or comments, please feel free to chip in.