I've noticed that a lot of examples dealing with Ruby Procs have the following & symbol in it.
# Ruby Example
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n, &callback)
n.times do
callback.call
end
end
shout_n_times(3, &shout)
# prints 'Yolo!' 3 times
My question is what is the functional purpose behind the & symbol? It seems that if I wrote the same exact code without &, it works as expected:
# Same code as previous without &
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n, callback)
n.times do
callback.call
end
end
shout_n_times(3, shout)
# prints 'Yolo!' 3 times
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.
As supplementary, I make myself remember &
as a conversion sign between block
and Proc
.
To convert a block
to Proc
def foo(&p)
puts p.class
end
foo {} # => Proc
To convert a Proc
to a block
def bar
yield "hello"
end
p = Proc.new {|a| puts a }
bar &p # => hello
Well, when you have a block, if you apply &
before block, it becomes Proc
object and vice-versa.
_unary &_
: it has something to do with converting things to and from blocks. If you take nothing else away from this, remember that when you see a unary “&” in Ruby, you are looking at making something into a block, or making a block into something.
In your first example, in this line shout_n_times(3, &shout)
, you are converting the Proc
object referenced by shoot
variable to a block
. and then in the method parameter list, you are converting it back to a Proc
object.
In your second example it works, because you are passing directly a Proc
object as a method argument, and then calling #call
on it.
The difference is that in your first example:
# Ruby Example
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n, &callback)
n.times do
callback.call
end
end
shout_n_times(3, &shout)
...your method call syntax allows you to rewrite the method definition like this:
shout = Proc.new { puts 'Yolo!' }
def shout_n_times(n)
n.times do
yield
end
end
shout_n_times(3, &shout)
--output:--
Yolo!
Yolo!
Yolo!
These two statements:
shout = Proc.new { puts 'Yolo!' }
...
shout_n_times(3, &shout)
...are equivalent to:
shout_n_times(3) do
puts 'Yolo!'
end
And writing yield() inside the method definition of shout_n_times() calls the block that is specified after the method call:
method call +--start of block specified after the method call
| |
V V
shout_n_times(3) do
puts 'Yolo!'
end
^
|
+--end of block
You see, a block is like a method, and a block gets passed as an invisible argument in the method call after which the block is written. And inside the method definition, whoever wrote the method definition can execute the block with yield(). Ruby's blocks are nothing more than a special syntax that allows you to pass a method as an argument to another method.
来源:https://stackoverflow.com/questions/28439734/purpose-of-ampersand-in-ruby-for-procs-and-calling-methods