Friday, April 14, 2006

Playing With Procs And Objects

One of the things that I love about Ruby is the ability to treat methods and blocks as the same:
def square(number)
number * number
end
method = method(:square)
block = lambda {|number| number*number}
numbers = [1, 2, 3]

#prepare to get the same answer
p numbers.collect(&method)
#result: [1, 4, 9]
p numbers.collect(&block)
#result: [1, 4, 9]

At first glance, you might think this happens because of method and block having a common method in :call. But, you would be wrong. Try this code and watch it fail:
#This fails
class Symbol
def call(*all)
method(self).call(*all)
end
end
p numbers.collect(&:square)
#result: ERROR!

Well, the first clue is the weird "&" in front in all calls. This converts a Proc object to a block to be passed into the call. The interpreter expects an object to be a Proc after the "&" or it complains you didn't give it one. What are we to do? Well, thankfully, the interpreter tries to do the conversion by calling :to_proc. Let's try it:
class Symbol
def to_proc
method(self).to_proc
end

end
p numbers.collect(&:square)
#result: [1, 4, 9]

This works like a charm! Now, by simply implementing :to_proc we can use any object to a call that expects block.
class MyComplicatedCalculation
#lots of instance variables..too heavy for a mere Proc
def to_proc
#calculations that could end world hunger go here
#For now, simply return a simple answer
lambda { | number | 42 }
end
end

p numbers.collect(&MyComplicatedCalculation.new)
#result: [42, 42, 42]

I do wish Ruby just allowed me to implement :call and be done with it, but implementing :to_proc isn't much more work. Have fun!

No comments: