Ruby symbols instead of blocks
Every wonder why this works? It’s straight forward and easy to read, but how does it work internally in Ruby?
[1, 2, 3].inject(&:+) # => 6
Meet &
The & operator in Ruby lets us go from Proc to block and vise-versa, but only in certain places.
In fact, there are two two places where we can use &
In a method definition &blk will turn the blk argument into a proc, allowing it to be called.
def block_to_proc &blk blk.call end block_to_proc { "hello" } # => "hello"
In a method call it coverts the argument into a block.
def proc_to_block yield end proc_to_block(&proc { "hello" }) # => "hello"
To Proc
Ruby has a lot of to_X methods. They are designed to express their receiver as another type.
"1".to_i + 2 # => 3
Well, to_proc exists too. Expressing non proc objects as procs? When and why would we ever do that? An example is our [...].inject(:+) piece. A quick, simple, shorthand way to express a block without having to write it all out.
Anytime we are trying to express an object as a proc Ruby will check to see if that object responds to to_proc. If it does it will use the return value to express the object as a proc. Since &object can covert procs to blocks the & is going to want the object to be a Proc before it does anything. So &object is going to really be &object.to_proc.
Symbol’s To Proc
Symbol’s to_proc is what allows us to pass it in place of a block. It might look a little strange at first, but once you see it used it becomes pretty cool.
class Symbol def to_proc proc { |obj, *args| obj.send(self, *args) } end end
This returns a proc that takes 2 parameters
- First is an object that will receive the method.
- The second is the arguments that will be passed into the method.
And when that proc is called
- Sends the original symbol to the object with the arguments.
So, :methods.to_proc basically builds this method.
# :methods.to_proc def methods_to_proc_when_called obj, *args obj.send(:methods, *args) end
Lets play with :symbol.to_proc in some funny ways.
my_little_proc = :methods.to_proc # my little proc is ready to call :methods on any object my_little_proc.call(String) # => returns an array all of String's methods
The Setup
We’re basically creating a proc from a symbol that can be used to send that symbol into any object. In fact, a more fitting name for my_little_proc would be something like call_my_symbol_on.
class String def introduce puts "Hi I'm #{self}" end def introduce_to name puts "Hi #{name}, I'm {self}" end end call_my_symbol_on = :introduce.to_proc call_my_symbol_on("ryan") # => "Hi I'm ryan"
Since the proc that to_proc returned is setup to take arguments.
call_my_symbol_on = :introduce_to.to_proc call_my_symbol_on("ryan", "steve") # => "Hi steve, I'm ryan"
If you think about it, this is really simple. call_my_symbol_on is just going to call introduce (because we used :introduce to create it) on the first parameter. It is going to send the 2nd+ parameters in as arguments.
With Map
Ok, lets get to the real world examples. We often see to_proc commonly used with enumerable. Lets say we want to introduce a bunch of names.
['ryan', 'steve', 'jill'].map(&:introduce) # => Hi I'm ryan # => Hi I'm steve # => Hi I'm jill
Is really
['ryan', 'steve', 'jill'].map(&:introduce.to_proc)
Which can be expressed as
['ryan', 'steve', 'jill'].map( & proc{ |obj, *args| obj.send(:introduce, *args) } )
And & is now going to covert that proc into a block. That line can now become
['ryan', 'steve', 'jill'].map{ |obj, *args| obj.send(:introduce, *args) }
And since map only passes one argument, the element, to its block there is really no need to express the additional arguments.
['ryan', 'steve', 'jill'].map{ |obj| obj.send(:introduce) }
Which is sending the message introduce our object, which we know is the same as
['ryan', 'steve', 'jill'].map{ |obj| obj.introduce }
You can see that by expanding and reducing the &:symbol notation we can end up with a very familiar call map with block.
Now with some *Args
So what about *args? We were able to drop it, because map only expects a block that will yield to one element. We need to find a common ruby method that yields more than one thing to a block.
How about inject? It’s block expects 2 parameters, result and element.
Enumerable.inject(start) { |result, element| … }
Lets do what we did above. But this time get from
# turn (1..10).inject(&:+) # into (1..10).inject do |result, element| result + element end
Here we go
(1..10).inject(&:+) (1..10).inject(&:+.to_proc) # can be expressed as (1..10).inject( &proc{ |obj, *args| obj.send(:+, *args) } ) # which & will covert into (1..10).inject( |obj, *args| obj.send(:+, *args) } ) # which we can convert to (1..10).inject do |obj, *args| obj.send(:+, *args) end # and then we can just rename our parameters to something more friendly (1..10).inject do |result, element| result.send(:+, element) end # => 55

There’s also
[5, 7, 8, 1].each(&method(:puts))which does puts(number) on each number.