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

断了今生、忘了曾经 提交于 2019-11-30 12:49:44

问题


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

回答1:


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.




回答2:


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



回答3:


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.




回答4:


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!