Purpose of & (Ampersand) in Ruby for Procs and Calling Methods

Purpose of & (ampersand) in Ruby for procs and calling methods

This article provides a good overview of the differences.

To summarize the article, Ruby allows implicit and explicit blocks. Moreover, Ruby has block, proc and lambda.

When you call

def foo(block)
end

block is just a simple argument of the method. The argument is referenced in the variable block, and how you interact with it depends on the type of object you pass.

def foo(one, block, two)
p one
p block.call
p two
end

foo(1, 2, 3)
1
NoMethodError: undefined method `call' for 2:Fixnum
from (irb):3:in `foo'
from (irb):6
from /Users/weppos/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'

foo(1, Proc.new { 1 + 1 }, 3)
1
2
3

But when you use the ampersand & in the method definition, the block assumes a different meaning. You are explicitly defining a method to accept a block. And other rules will apply (such as no more than one block per method).

def foo(one, two, &block)
p one
p block.call
p two
end

First of all, being a block, the method signature now accepts "two parameters and a block", not "three parameters".

foo(1, 2, Proc.new { "from the proc" })
ArgumentError: wrong number of arguments (3 for 2)
from (irb):7:in `foo'
from (irb):12
from /Users/weppos/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'

That means, you have to force the third argument to be a block passing the parameter with the ampersand.

foo(1, 2, &Proc.new { "from the proc" })
1
"from the proc"
2

However, this is a very uncommon syntax. In Ruby, methods with blocks are generally called using {}

foo(1, 2) { "from the block" }
1
"from the block"
2

or do end.

foo(1, 2) do
"from the block"
end
1
"from the block"
2

Let's jump back to the method definition. I previously mentioned that the following code is an explicit block declaration.

def foo(one, two, &block)
block.call
end

Methods can implicitly accept a block. Implicit blocks are called with yield.

def foo(one, two)
p yield
end

foo(1, 2) { "from the block" }

You can check the block is passed using block_given?

def foo(one, two)
if block_given?
p yield
else
p "No block given"
end
end

foo(1, 2) { "from the block" }
=> "from the block"

foo(1, 2)
=> "No block given"

These block-related features would not be available if you declare the "block" as a simple argument (hence without ampersand), because it would just be an anonimous method argument.

Unary Ampersand Operator and passing procs as arguments in Ruby

This example beautifully ties together multiple Ruby concepts. Because of that, I will try to explain all of them.

  1. Blocks

Methods in Ruby can accept blocks (pieces of code) in elegant matter:

def run_code
yield
end

run_code { puts 42 } # => prints 42

  1. Procs are similar to blocks, but they are actual addressable objects:
deep_thought = proc { puts 42 }
deep_thought.call # => prints 42

  1. You can turn a proc into a block when calling a method with the & operator:
def run_code
yield
end

deep_thought = proc { puts 42 }
run_code(&deep_thought) # => prints 42

  1. Procs and blocks can accept arguments:
def reveal_answer
yield 5_000
end

deep_thought = proc do |years_elapsed|
years_elapsed >= 7_500_000 ? 42 : 'Still processing'
end

reveal_answer(&deep_thought) # => 'Still processing'

  1. You can turn a block into proc using & in the method signature:
def inspector(&block)
puts block.is_a?(Proc)
puts block.call
end

inspector { puts 42 } # => prints true and 42
inspector(&proc { puts 42 }) # => the same

  1. Symbol#to_proc creates a proc that calls methods with the same name on the object:
class Dummy
def talk
'wooooot'
end
end

:talk.to_proc.call(Dummy.new) # => "wooooot"

In other words,

:bar.to_proc.call(foo)

is pretty much equivalent to

foo.bar

  1. BasicObject#method_missing:

When you try to call a method on an object, Ruby traverses it's ancestor chain, searching for a method with that name. How the chain is constructed is a different topic, lengthy enough for another day, the important thing is that if the method is not found til the very bottom (BasicObject), a second search is performed on the same chain - this time for a method called method_missing. It gets passed as arguments the name of the original method plus any argument it received:

class MindlessParrot
def method_missing(method_name, *args)
"You caldt #{method_name} with #{args} on me, argh!"
end
end

MindlessParrot.new.foo # => "You caldt foo with [] on me, argh!"
MindlessParrot.new.bar :baz, 42 # => "You caldt bar with [:baz, 42] on me, argh!"

So what does all this mean in our specific case? Lets assume for a second there was no protected.

translator.speak(&:spanish)

calls the method Translator#speak with :spanish converted to block.

Translator#speak takes that block and transforms it to a proc, named language, and calls it, passing self as argument.

self is an instance of Translator, therefore, it has the methods speak, french, spanish, turkey and method_missing.

And so:

Translator.new.speak(&:spanish)

is equivalent to:

:spanish.to_proc.call(Translator.new)

which is equivalent to:

Translator.new.spanish

giving us "hola".


Now, taking the protected back, all the language methods of our translator object are still present, but they can not be directly accessed by outsiders.

Just as you can't call

Translator.new.spanish

and expect "hola" back, you can't call

Translator.new.speak(&:spanish)


And since the method is not directly accessible, it is considered not found and method_missing is called, thus giving us "awkward silence".

What does the expression &Proc.new do in a method?

& has a special meaning in argument list - when used as prefix for Proc object it passes it as block to method being called. Within method body it's just a binary operator.

What does map(&:name) mean in Ruby?

It's shorthand for tags.map(&:name.to_proc).join(' ')

If foo is an object with a to_proc method, then you can pass it to a method as &foo, which will call foo.to_proc and use that as the method's block.

The Symbol#to_proc method was originally added by ActiveSupport but has been integrated into Ruby 1.8.7. This is its implementation:

class Symbol
def to_proc
Proc.new do |obj, *args|
obj.send self, *args
end
end
end

Passing procs and methods

Let's step through it.

doubleIt = Proc.new do |x|
x + x
end
#=> #<Proc:0x00000002326e08@(irb):1429>

squareIt = Proc.new do |x|
x * x
end
#=> #<Proc:0x00000002928bf8@(irb):1433>

proc1 = doubleIt
proc2 = squareIt

compose returns the proc proc3.

proc3 = Proc.new do |x|
proc2.call(proc1.call(x))
end
#=> #<Proc:0x000000028e7608@(irb):1445>

proc3.call(x) is executed in the same way as

proc3_method(x)

where

def proc3_method(x)
y = proc1.call(x)
proc2.call(y)
end

When x = 5,

y = proc1.call(5)
#=> 10
proc2.call(10)
#=> 100

proc3_method(5) therefore returns 100, as does proc3.call(5).

Difference between block and &block in Ruby

block is just a local variable, &block is a reference to the block passed to the method.

def foo(block = nil)
p block
end

foo # => nil
foo("test") # => test
foo { puts "this block will not be called" } # => nil

def foo(&block)
p block
end

foo # => nil
foo("test") # => ArgumentError: wrong number of arguments (1 for 0)
foo { puts "This block won't get called, but you'll se it referenced as a proc." }
# => #<Proc:0x0000000100124ea8@untitled:20>

You can also use &block when calling methods to pass a proc as a block to a method, so that you can use procs just as you use blocks.

my_proc = proc {|i| i.upcase }

p ["foo", "bar", "baz"].map(&my_proc)
# => ["FOO", "BAR", "BAZ"]

p ["foo", "bar", "baz"].map(my_proc)
# => ArgumentError: wrong number of arguments (1 for 0)

The variable name block doesn't mean anything special. You can use &strawberries if you like, the ampersand is the key here.

You might find this article helpful.

Converting array using procs and symbols

What happens is that the syntax &:to_s is another way constructing a block that calls the method that matches the symbol, on every item in the array. In your case, it is basically the same as writing:

strings_array = numbers_array.map { |number| number.to_s }

Here is another example:

strings_array = numbers_array.map(&:to_f)
# Same as
strings_array = numbers_array.map { |number| number.to_f }

For further explanation regarding the & sign, look at how you would define a method that accepts a block as an argument.

def map(&block)
# ....
end

Now look at the different ways we could invoke this method that all will yield the same result.

# Block using bracket notation
map { |n| n.to_i }

# Block using do ... end notition
map do |n|
n.to_i
end

# When using ampersand, ruby will try to treat the following as a
# proc and use to proc as the block for the method. If we pass a proc
# to it, then no problem.
p = proc { |n| n.to_i }
map(&p)

# When using ampersand with a symbol (or any other non-proc objects),
# ruby will try to call the to_proc method on the object and use that
# as a block. And in the scenario of passing a symbol, the Symbol
# object will return a proc that calls the method that matches the symbol.
:to_i.to_proc => #<Proc:...>
map(&:to_i)


Related Topics



Leave a reply



Submit