Problems with arity when converting a method to a block/proc that accepts an array param

Posted by Tim Connor Sat, 17 Jan 2009 04:55:00 GMT

Some consider this hacky, but while working on cleaning up test_benchmark I wanted to pass a named method as a block to a map. In the simplest case you just grab a method reference via object.method(:method_name) and to_proc it with an ampersand. In this case the method definition is basically just a way to store a proc, and this could be replaced by just storing a proc in an instance variable. But it works, both with the method called directly, and passed as a proc/block:

benchmarks = [["a slow test",2.0],["a fast test",0.001]]
def by_time(a,b)
  b[1] <=> a[1]
end
benchmarks.sort { |a,b| by_time(a,b) } #works
benchmarks.sort(&method(:by_time)) #works

I discovered you can have problems with arity, though, if the block method is passed an array, which is then expanded out to array.length params, giving you an ArgumentError. For instance, if I am again working with doubles for my benchmark rows:

benchmarks = [["a slow test",2.0],["a fast test",0.001]]
def format_row(tuple)
  ("%0.3f" % tuple[1]) + " #{tuple[0]}"
end
benchmarks.map { |tuple| format_row(tuple } #works
benchmarks.map(&method(:format_row))
# ArgumentError: wrong number of arguments (2 for 1)

Thanks to Evan Phoenix for pointing out I just need to use a glob param in my method. This won’t work with the direct call anymore, as the tuple gets wrapped in another array by the globbing:

benchmarks = [["a slow test",2.0],["a fast test",0.001]]
def format_row(*tuple)
  ("%0.3f" % tuple[1]) + " #{tuple[0]}"
end
benchmarks.map(&method(:format_row)) #works
benchmarks.map { |tuple| format_row(tuple }
#Blows up on an invalid index access, because tuple inside format_row is now [["a slow test",2.0]] for the first call

To work around this, in this case, it works to just flatten the array.

def format_row(*tuple)
  tuple.flatten!
  ("%0.3f" % tuple[1]) + " #{tuple[0]}"
end
benchmarks.map(&method(:format_row)) #works
benchmarks.map { |tuple| format_row(tuple } #works

I guess the general conclusion to go with, is be careful doing tricky things like using method references as procs. Especially keep a careful eye on the method signature and how arrays can mess with your arity.

Leave a comment

Comments