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.
- Blocks
Methods in Ruby can accept blocks (pieces of code) in elegant matter:
def run_code
yield
end
run_code { puts 42 } # => prints 42
- Procs are similar to blocks, but they are actual addressable objects:
deep_thought = proc { puts 42 }
deep_thought.call # => prints 42
- 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
- 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'
- 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
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
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
How to Dump an Http Request from Within Sinatra
Converting Utc Timestamp to Iso 8601 in Ruby
Finding Nil Has_One Associations in Where Query
Ruby/Rails CSV Parsing, Invalid Byte Sequence in Utf-8
How to Get Constants Defined by Ruby's Module Class via Reflection
Ruby Loading Config (Yaml) File in Same Dir as Source
How to Redefine a Ruby Constant Without Warning
Ruby: Module, Require and Include
Rails: Testing Named Scopes with Rspec
Monkey-Patching VS. S.O.L.I.D. Principles
Phusion Passenger Error: You Have Activated Rack 1.2.1, But Your Gemfile Requires Rack 1.2.2
Automatic Code Quality Tool for Ruby
How to Convert Array of Activerecord Models to CSV
Will Uuid as Primary Key in Postgresql Give Bad Index Performance