Ruby中&符号在procs和调用方法中的作用是什么?

25

我注意到很多关于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

1
不要在括号前加空格! :) - Sergio Tulentsev
抱歉,这是我的习惯,来自JavaScript的世界 :) 我会在我的示例中进行更正。 - wmock
http://ablogaboutcode.com/2012/01/04/the-ampersand-operator-in-ruby/ - user2864740
1
@wmock,我来自js的世界,我不会在括号前面加空格 - 我也记不起有人这样做过。 - 7stud
在第一个情况下,您可以调用您的方法 shout_n_times(3) { shout.call }shout_n_times(3, &shout),因为它完全相同,但在第二个情况下不行,因为它正在等待一个参数! - Joel AZEMAR
4个回答

42

这篇文章提供了有关Ruby中&参数的详细概述。

总结一下文章的内容,Ruby允许隐式和显式块。此外,Ruby还有块、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

首先,作为一个块,该方法签名现在接受“两个参数和一个块”,而不是“三个参数”。

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”声明为简单参数(因此没有&符号),则将无法使用与块相关的特性,因为它只是一个匿名方法参数。

感谢您的详细回复和示例 - 真的帮助我更好地理解了这个问题! - wmock
我发现那篇链接文章读起来令人沮丧和不愉快。感谢你提供了一个非常棒的替代品。 - Hovis Biddle

10

作为补充,我让自己将&视为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

这是我在任何地方看到的最好的解释。谢谢! - Jason Swett

5

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

_unary &_:它与将事物转换为块以及从块中转换出来有关。如果您从中没有获得其他任何内容,请记住,在Ruby中看到一元“&”时,您正在将某些东西转换为块或将块转换为某些东西。

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

在您的第二个示例中,它可以正常工作,因为您直接将Proc对象作为方法参数传递,然后调用#call


我理解的是,如果我有一个 Proc 对象,并在前面加上 &,那么结果可以用作方法的代码块?另一方面,如果我有一个代码块,然后在前面加上 &,我可以将结果用作 proc 吗?谢谢! - wmock
3
& 一元前缀运算符仅限于参数列表和形参列表。在形参列表中,它表示“将传递的块转换为 Proc 并将其绑定到名称”。在参数列表中,它表示“将 Proc 转换为块,就好像它已经作为文本块传递”,而且如果传递的对象不是 Proc,Ruby 将首先调用 to_proc 将其强制转换为 Proc,这使得诸如 Symbol#to_proc 等巧妙的技巧成为可能。 - Jörg W Mittag

3

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

# 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的块只不过是一种特殊的语法,允许你将一个方法作为参数传递给另一个方法。

感谢提供这个具有说明性的例子!非常有帮助! - wmock

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接