codahale.com٭blog

Coda Hale lives in Berkeley, CA, where he writes about Ruby on Rails, usability, web design and development, and the occasional bit about bicycles.

Stupid Ruby Tricks: String#to_proc

I was thinking about one of the cool bits of ActiveSupport, Symbol#to_proc, which allows you to do this:

[1, 2, 3].map(&:to_s) #=> ["1", "2", "3"]

That’s useful for sorting, enumerating, mapping, etc. Quite handy, and it saves you the trouble of writing a block to return a single value. I was recently someplace with a fair bit of time to kill and no reading material, and I began to wonder… how does this work?

Magic via one of the few typographic characters I can’t even write

Oh, the ampersand. Curly figure of import. That little pretzely freak is the sin qua non for this entire operation. You can tell, because Array#map acts all angry when you try to just pass it a Symbol:

irb(main):001:0> [1, 2, 3].map(:to_s)
ArgumentError: wrong number of arguments (1 for 0)
        from (irb):1:in `map’
        from (irb):1

The ampersand is how Ruby denotes a block variable.

Let’s try to pass our block in a different way:


my_block = lamdba{ |x| x.to_s }
[1, 2, 3].map(my_block)

Ruby will raise an ArgumentError because it’s trying to pass your block as a regular method parameter, rather than as a block parameter. Let’s try passing it as a block parameter:

[1, 2, 3].map(&my_block) #=> ["1", "2", "3"]

Sweet! It worked!

Now we’ve got a better of idea of what the hell we’re actually doing. When we write map(&:to_s), we’re saying “call the map function and pass it this block.” But a Symbol isn’t a block!

Very true, and what does Ruby try to do when presented with a type mismatch? Coerce! How? Conversion methods! Anything with a to_proc method can be converted to a proc. Our first example can be rewritten as follows:

[1, 2, 3].map(&:to_s.to_proc) #=> ["1", "2", "3"]

This means that anything with a to_proc method can partake of this lovely syntactic sugar.

Pushing the limits for no apparent reason

And now for the inevitable section of any article on programming that I write: taking things too far.

class String
  def to_proc
    eval "Proc.new { |*args| args.first#{self} }"
  end
end

robots = []
robots << { :name => ‘Jeevesotron’,  :type => ‘robo-butler’ }
robots << { :name => ‘Crushmonster’, :type => ‘robo-baby-sitter’ }
robots << { :name => ‘Fluffy’,       :type => ‘doombot’ }

robots.map(&’[:name].upcase’) #=> ["JEEVESOTRON", "CRUSHMONSTER", "FLUFFY"]

It may not be useful, but it sure as hell is fun.

6 comments »