Originally posted as a private gist
Before I get started, the inspiration behind this was a piece of code written to solve a Ruby exercise: Write a method reverse_words which takes a sentence as a string and reverse each word in it.
The solution that set me on a journey of diving deeper into closures was this one, by Daniela Grossman:
def reverse_words(str)
str.split(' ').map(&:reverse).join(' ')
endCompared to my solution:
def reverse_words(str)
str.split(' ').map{|i| i.reverse }.join(' ')
endI wanted to know what that & meant and this lead me to learn more about closures, blocks, procs, and lambdas in Ruby.
tl;dr: The first solution is a more concise way to write the second. See: Symbol#to_proc
Blocks (do…end or {}) in Ruby are closures. This gives them special properties, mainly that they can access variables that are outside of their scope, i.e. the variables are assigned before you enter the block.
To illustrate what that means, check this out. In the following example I define a new class Calculator and define an instance variable @numbers.
class Calculator
def initialize(numbers)
@numbers = numbers
end
def multiply(x)
@numbers.map { |i| i * x }
end
endNow, let's say we ran this code:
>> calc = Calculator.new([2, 3, 4, 5])
>> p calc.multiply(2)Our result, unsurprisingly is:
=> [4, 6, 8, 10]While we may take this sort of behavior for granted, something special is happening here. The { } block that is passed into the map method above is able to access the x variable. The block has access to variables defined in the surrounding scope.
The i, however, is defined within the local scope of the block. This means that only the block has access to it.
In this example from Practicing Ruby, a Vector object is created and a method * is defined.
If you are not familiar with linear algebra or scalar multiplication: scalar multiplication.
class Vector
def initialize(data)
@data = data
end
def *(num)
@data.map { |e| e * num }
end
end
>> Vector.new([1,2,3]) * 7
=> [7, 14, 21]We can see how the closure above is able to access num, even though it is defined outside its local scope.
Blocks like the one in the example above are short-lived. Blocks are not objects, but you can turn them into objects and pass them around like any old object. A block that has been turned into an object is a Proc object.
For example,
>> double = Proc.new { |i| puts 2 * i }
>> double.call(5)
=> 10
>> puts double.class
=> Proc
If you're struggling at this point, please try to follow along. I am not going to try to cover the entire topic of blocks and closures in depth. I think that Programming Ruby 1.9 does a very good job of this and I highly suggest reading the two chapters on this.
This is certainly one of the core concepts in Ruby. Understanding these core concepts is a pre-requisite to becoming a master Rubyist.
Now, let's go back to what started this whole discussion…
If we go back to Daniela's solution, at this point we might be able to make sense of what is happening.
def reverse_words(str)
str.split(' ').map(&:reverse).join(' ')
endThe map method is expecting a block. If you look at my solution and several others, we've done this by explicity passing this {|i| i.reverse} block to the method.
To demonstrate the concept of passing a block as a parameter, I'll borrow from an example in Programming Ruby 1.9 (p.86):
def create_block_object(&block)
block
endThe & prefix is telling the method to expect a block as a parameter. During runtime, when that method is called, Ruby will take the parameter passed to the method and convert it to a Proc object.
This specific method, create_block_object, will just return the block passed to it as an object.
Here it is in action:
>> bo = create_block_object { |param| puts "You called me with #{param}" }
>> bo.call 99
=> You called me with 99
>> bo.call "cat"
=> You called me with cat
>> puts bo.class
=> ProcAt this point it should be clear that map expects a block and the & is doing something special. We just learned that if you put & in front of a parameter, we are telling Ruby to convert that parameter into a Proc. We also know that Procs are just blocks converted to objects.
The & in front of a parameter is a nice way of telling Ruby to use call the method #to_proc on the parameter. #to_proc converts an object to a Proc object (the same way we would use `to_s to convert something to a String).
I still haven't explained how Daniela's solution works. In her solution, she is passing a symbol, :reverse prefixed with the & and somehow the map method knows to apply the reverse method on each element that it iterates over.
The beauty comes from the implementation of Symbol#proc, which we can find in the Ruby documentation:
Symbol#proc: Returns a Proc object which respond to the given method by sym.
This means that the Proc object created will apply the method named by the symbol to each of the objects that is passed to it as a parameter. Therefore, this code
str.split.map(&:reverse)is exactly the same as writing
str.split.map { |i| i.reverse }In addition to the soures I've mentioned above, here some links that truly helped me understand the concepts I've overviewed
- Blog post by Dave Thomas (co-author of Programming Ruby) that covers Symbol#to_proc
- Practicing Ruby: "A unique journal curated by Gregory Brown" (author of Ruby Best Practices)