why Ruby Symbol​#to_proc works, and more.()

Lets embark on a _why themed quest through Ruby's magical Symbol#to_proc. We will slay syntax dragons by deflating politicians, battle understanding with chrysanthemums, and journey further to cook up some funky fresh fish.

Hot Air

We've stumbled into a squawk of politicians! Look, they're surrounded by hot air:

politicians = ["    Ronald Trump    ", "  Tony Blare "]

We should try to get rid of that air:

politicians.map { |a| a.strip }
#=> ["Ronald Trump", "Tony Blare"]

Yep, making them strip should deflate their egos a bit. String#strip method gets rid of space surrounding a string. But what of this little treasure, map? Array#map takes a block, and runs the block for each item in the array passing in that item as a variable. Now, what's a block?

Blocks and Procs

It seems Ruby has blocks, procs, and lambdas with subtle differences, but they're similar. A block is syntax for passing a proc to a method as the "block argument" which comes last. We'll create some Procs:

# creating a proc on one line
tasty = proc { puts "potato wedgie" }

# creating a proc on multiple lines
tasty = proc do
  puts "potato wedgie"
end

# the `Kernel#proc` method is a shortcut for
tasty = Proc.new { puts "potato wedgie" }

# now run the proc
tasty.call
#=> potato wedgie

Both brackets or do/end have the same result here. There is a slight difference between bracket and do end syntax but you generally don't need to worry about it; just use brackets for single line, do/end for multi-line.

Alrighty. The brackets (or the do/end) part above are the blocks. Blocks are syntax to make procs. You can't just have a block on it's own, it would get lonely. Pass blocks to methods so they may keep each other company. Engage vision units and absorb:

# define method that takes a block
def do_a_thing(thing = nil, &block)
  p block.class
  block.call(thing)
end

# call method with a block
do_a_thing { puts "chicken scrunchie" }
#=> Proc
#=> chicken scrunchie

# call method with a proc
do_a_thing(&tasty)
#=> Proc
#=> potato wedgie

# use the supplied parameter in the block!
do_a_thing("Meta") { |x| puts x }
#=> Proc
#=> Meta

When defining a method we use a lovely little ampersand (&) to say that the last (and only last) argument is to be a block. And then it is so captured and made a Proc.

When calling a method we can give it a block directly, or pass in a Proc using our ampersand operator which in this case turns it into a block.

In the last example above you can see how a proc can be called with arguments, those arguments are passed into the block within the pipe | characters (imagine the data sliding down the pipe), and those arguments can be called whatever you like.

I think we can now understand the syntax of the windy politicians example at the top. We're giving the map method a parcel of code to run for each item in the array. Map then passes the item into the pipe as the variable name we choose (a in this case). Finally, map stores the result of running the block on each item in a new array, which it returns.

Unlike most other languages, Ruby has implicit return. We didn't need to utter return a.strip because loyal Ruby returns the value of the last statement in a block for us; just a.strip will do.

to_proc or not to proc?

Hey I've found more places where we want to perform an operation on every item in an array. Like seeing if everything is nice and even:

[2, 4, 6, 7].all? { |n| n.even? }
#=> false

Or getting rid of pesky nils:

[1, "hello", nil, :foo, 0, false].reject { |i| i.nil? }
#=> [1, "hello", :foo, 0, false]

We've got ourselves a pattern. Pass a variable into block, call method on it:

do_stuff { |variable| variable.method }

As good little programmer elves we want to save our magical fingers by reducing redundant code. That variable is repeated, and we can eliminate it using a clever feature of Ruby:

do_stuff(&:method)

Which means we can deflate our politicians even more succinctly!

politicians.map(&:strip)
#=> ["Ronald Trump", "Tony Blare"]

# and the examples above...

[2, 4, 6, 7].all?(&:even?)

[1, "hello", nil, :foo, 0, false].reject(&:nil?)

Do a little dance, mmm that's nice. So what's happening?

We discovered that in this context ampersand turns a proc into a block, but we only have a :symbol. A Symbol is like a String; neither of those are Procs! Well the thrifty ampersand must be doing something extra when it is given anything that's not a proc—it turns it into one by calling to_proc on it! Any Class can have a to_proc method so you can add one to your own clever classes if you like.

The Symbol#to_proc method is built into Ruby (since 1.8.7) so it's written in torturous C code. But it can actually be written in pure Ruby code and in fact before Ruby 1.8.7 it was; the implementation popularized by Rails was thus:

def to_proc
  Proc.new { |*args| args.shift.__send__(self, *args) }
end

It creates a new proc that takes a variable number of arguments, then removes the first argument (with shift) and calls the method on it referred to by self (which is a symbol) with the remaining arguments.

The call on the first argument explains why the first simple examples above work, because the first (and only) argument is the item in the array and then the symbol is the method to call on that item.

Symbol#to_proc Haddock

Thanks Ruby helpline.

Desert

I love Symbol#to_proc, it allows you to write some very short, beautiful code. Accept its symbolly embrace. But there are situations where it falls. Like chaining multiple method calls:

["banana", "  hammock! "].map { |s| s.strip.capitalize }
#=> ["Banana", "Haddock!"]

Or passing static parameters to the method, for when something is fishy:

["banana", "hammock!"].map { |s| s.gsub(/mm/, "dd") }
#=> ["banana", "haddock!"]

But symbols can't take arguments so the latter wouldn't work, and the ampersand must come before the first object at the end of method call arguments, so you can't chain them.

Despair not yet! There is actually a different ampersand operator that is a method not language syntax like the above. This ampersand comes between objects and is just a method called on the left one and passed the right one. This method can be monkeypatched into Symbol to allow Symbol#to_proc style chaining!

class Symbol
  def &(other)
    proc { |*args| other.to_proc.call(to_proc.call(*args)) }
  end
end

["banana", "  hammock! "].map(&:strip&:capitalize)
#=> ["Banana", "Hammock!"]

It turns the left and the right into procs, calls the left first then calls the right with the result of the left as its argument. Symbol#to_proc is never called because our & method on Symbol already returns a proc so it just gets turned straight into a block. Here are different ways of writing it to demystify the magic:

["banana", "  hammock! "].map &(:strip & :capitalize)
["banana", "  hammock! "].map &(:strip.&(:capitalize))

We're on a roll now, oh yes, lets hack on arguments!

class Symbol
  def call(*defaults)
    proc { |*args| args.shift.__send__(self, *defaults) }
  end
end

["banana", "hammock!"].map(&:gsub.(/mm/, "dd"))
#=> ["banana", "haddock!"]

Beautiful. Apart from the dot it almost looks like a language feature, but actually we've just added it thanks to the flexibility of Ruby. The .() syntax is a shortcut for .call(), as you might have guessed from the method we defined.

Symbol#to_proc Haddock

I've wrapped up these handy extensions (plus a few bonuses) with brown paper and released them as a Gem fancytoproc for you to indulge upon. In the spirit of why the lucky stiff, why don't you give these enhancements a twirl? I don't know if he would have endorsed them in particular, but he loved experimenting to make coding more fun.