In Ruby, &method passes you!

Most Rubyists would know what the following does.

[1, 2, 3].map(&:to_s)

It takes the array of numbers, calls to_s on each item, and returns an array of the return values.

But what if you want to do the opposite? Rather than calling a method on the object, what if you want to call a method with the object as a parameter? Simple!

["1", "2", "3"].map(&method(:Integer))

Wait … what?

I’m sure you all understand how it works, because you already use map(&:to_s), and none of you would use something you don’t understand, right?

Who am I kidding?

I’ll explain how map(&:method_name) works, and then how map(&method(:method_name)) works.

When you use & (ampersand) in front of the last variable in a method call, you’re saying that what’s passed in shouldn’t be treated as an object, but treated like a piece of code – a proc.

Symbols aren’t procs, but they can be converted into them by Ruby. You can see for yourself by running to_proc and call on it with the following:
# my_proc will be something like #<Proc:0x00000001014c39c8@(irb):35>
my_proc = :reverse.to_proc
# This causes "abc".reverse to be called, which returns "cba"
my_proc.call("abc")

As Ruby calls `to_proc` if an ampersand parameter isn’t a proc, the following

def call_me_three_times(parameter_1)
  puts((yield parameter_1).inspect)
  puts((yield parameter_1).inspect)
  puts((yield parameter_1).inspect)
end

call_me_three_times("abc", &:reverse)

will print the string reversed three times.

Now that the & part is understood, let’s look at the method part.

When you call method(:method_name), you get back an object of class Method that represents the method.
# Integer(string) is a method kind of like String#to_i, but 100% more awesome.
# Look it up if you haven't done so already.
# PS: don't worry about it starting with a capital letter!
# another_proc will look like #<Method: Object(Kernel)#Integer>
another_proc = method(:Integer)
another_proc.call("42") # Returns 42

and as `Method#to_proc` exists, the `call_me_three_times` method will convert the method object into a proc and will run it without any complaints.

When you run `call_me_three_times("42", &another_proc)`, it’ll print 42 (not “42”) three times.

But wait … there’s more!

The method doesn’t even have to be from your own object – it can be something else’s. For example, if you wanted to read in a series of files, you could do

filenames = ["a.txt", "b.txt", "c.txt"]
texts = filenames.map(&File.method(:read))

File.method(:read) turns the read singleton method of the File class into an object, and then (with the help of Method#to_proc) that object gets passed to map as something that should be executed on each filename.

Downsides

Firstly, you may need to document what &method does. I’ve been programming in Ruby for over three years, and hadn’t come across this trick until I noticed it on Stack Overflow. However, it is mentioned in the Pickaxe, now that I looked it up.

Second, there may be performance penalties for this approach. If hardly anybody has used this approach, it’s unlikely to have been optimized.

Third, making code shorter isn’t always a good thing. It may make smelly code seem less smelly. If you’re doing twenty different things on one line, the answer isn’t to cram things up to make the line shorter, but to refactor that line into something more logical.

Fourth, I haven’t worked out how to abuse this trick fully. Under Ruby 1.9, if foo takes two arguments, I don’t know how to do [["a", "b"], ["c", "d"]].map(&method(:foo)) yet. That code works under 1.8, however.

(Apologies for the awful appearance of the code – I haven’t worked out how to make wordpress use Github gists without adding a plugin)

7 Responses to “In Ruby, &method passes you!”

  1. Peter Says:

    Hm. It was interesting to Read

  2. Patrick Ma Says:

    Yup, good read. Post more ruby tips 😉

  3. Serguei Filimonov Says:

    Hi Avdi,

    I happened to read both this post and a draft of your “Object on Rails” book. Do you think &method provides a good tool for left-to-right “decoration” of data and addition of behaviour?

    This gist kinda exemplifies my point: https://gist.github.com/2381626

    If placing readability of flow of data, then
    originalData.pat(&method(:FooType)).pat(&method(:BarType)).message
    is, in a way, better than
    BarType.new(FooType.new(originalData)).message

    What do you think?

  4. andrewjgrimm Says:

    Hi Serguei. I am a Grimm, I am an A. grimm, but I’m not Avdi Grimm! 🙂

    • Serguei Filimonov Says:

      Oh damn, sorry Andrew. I’m still interested in what YOU think about the &method being used to write flow of data left-to-right through decorations.

  5. andrewjgrimm Says:

    I don’t know much at all about design patterns and the like in Ruby. I think me answering would make the internet a worse place. 🙂

  6. Madhan a Says:

    really nice article. Thanks for sharing. Keep it up

Leave a reply to andrewjgrimm Cancel reply