alan dipert RSS

github / twitter / resume / email
Jan
17th
Sun
permalink

Passing Methods like Blocks in Ruby

Ruby’s Functions

Ruby has four flavors of function, each with various nuances: methods, blocks, Procs, and lambdas.

A Simple Example

The object Array has a bunch of methods on it that take blocks.  Normally, you write these blocks inline:

[1,2,3].map{|x| x+1} # → [2,3,4]

But what if you have a method you’d like to pass to map?

def addone(n)
  n+1
end
[1,2,3].map(addone) # → Doesn't work

The above example doesn’t work because map is expecting a block.  We’re not passing it one; instead, we’re invoking addone with no arguments and attempting to pass the result of that invocation to map.

To get a value representing the addone method, we can use Object.method, which takes the symbol of the name of the method as an argument:

def addone(n)
  n+1
end
[1,2,3].map(method(:addone)) # → Still doesn't work

That still doesn’t work, but we’re close.  Map expects a block, but we’re passing it a Method. The key to this conversion is the unary ampersand &, which will convert anything to a Proc that has a to_proc method, and then convert any Proc to a block.

Let’s give it another shot:

def addone(n)
  n+1
end
[1,2,3].map(&method(:addone)) # → [2,3,4]

Voila! The point? This is just another way to consolidate and modularize code.  If you find yourself writing the same block over and over, and if it makes sense, you can write a method and reference to it.

Getting Complicated

Here’s a Polish notation calculator that groups operators and arguments as S-expressions, with Arrays:

def evl(exp)
  if exp.is_a? Array
    exp.slice(1..-1).map(&method(:evl)).inject(exp.first)
  else
    exp
  end
end

evl(3) # → 3
evl([:+, 4, 5]) # → 9
evl([:+, 1, 2, [:-, 10, 7]]) # → 6

If the input to evl is an Array, evl maps every element of the Array except the first to itself, and then reduces the resulting Array with the first term, which should be a Symbol representing a binary function.  evl takes advantage of the fact that inject automatically casts Symbols to blocks.

This example is different from the one above, in that &method(:evl) occurs inside of evl, which doesn’t actually work in Ruby 1.8.7.  It seems to work in 1.9.1, though.

Comments (View)
blog comments powered by Disqus