Ruby 中 &(&符号)的用途用于过程和调用方法

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

我注意到很多处理 Ruby Procs 的例子都有下面的 & 符号。

# 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

我的问题是 & 符号背后的功能目的是什么?似乎如果我不带 & 编写完全相同的代码,它会按预期工作:

# 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

好吧,当你有一个 时,如果你在块之前应用 &,它就会变成 Proc 对象,反之亦然。

_unary &_:跟方块之间的东西转换有关。如果您对此一无所知,请记住,当您在 Ruby 中看到一元“&”时,您正在考虑将某物变成一个块,或者将一个块变成某物。

在您的第一个示例中,在这一行 shout_n_times(3, &shout) 中,您将 shoot 变量引用的 Proc 对象转换为 block。然后在方法参数列表中,将其转换回 Proc 对象。

在你的第二个例子中它起作用了,因为你直接传递了一个 Proc 对象作为方法参数,然后在它上面调用 #call

This article 很好地概述了差异。

总而言之,Ruby 允许隐式和显式块。此外,Ruby 有 block、proc 和 lambda。

当你打电话时

def foo(block)
end

block只是方法的一个简单参数。该参数在变量 block 中被引用,您如何与它交互取决于您传递的对象类型。

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

但是当您在方法定义中使用与号 & 时,该块具有不同的含义。您正在 明确地 定义接受块的方法。其他规则将适用(例如每个方法不超过一个块)。

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

首先,作为一个块,方法签名现在接受"two parameters and a block",而不是"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>'

这意味着,您必须强制第三个参数成为一个块,通过与号传递参数。

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

但是,这是一种非常不常见的语法。在Ruby中,通常使用{}

调用带块的方法
foo(1, 2) { "from the block" }
1
"from the block"
2

do end.

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

让我们跳回方法定义。我之前提到过以下代码是 显式块声明 .

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

方法可以隐式接受块。使用 yield.

调用隐式块
def foo(one, two)
  p yield
end

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

您可以使用 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"

如果您将 "block" 声明为简单参数(因此没有&符号),这些与块相关的功能将不可用,因为它只是一个匿名方法参数。

区别在于你的第一个例子:

# 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)

...您的方法调用语法允许您像这样重写方法定义:

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!

这两个语句:

shout = Proc.new { puts 'Yolo!' }
...
shout_n_times(3, &shout)

...等同于:

shout_n_times(3) do
  puts 'Yolo!'
end

并在 shout_n_times() 的方法定义中写入 yield() 调用方法调用后指定的块:

   method call    +--start of block specified after the method call
      |           |    
      V           V
shout_n_times(3) do
  puts 'Yolo!'
end
 ^
 |
 +--end of block

你看,块就像一个方法,块在方法调用中作为不可见参数传递,之后块被写入。在方法定义内部,编写方法定义的人可以使用 yield() 执行块。 Ruby 的块只不过是一种特殊的语法,它允许您将一个方法作为参数传递给另一个方法。

作为补充,我让自己记住&作为blockProc之间的转换符号。

block转换为Proc

def foo(&p)
  puts p.class
end

foo {} # => Proc

Proc 转换为 block

def bar
  yield "hello"
end
p = Proc.new {|a| puts a }

bar &p # => hello