2015-02-10 48 views
15

我注意到很多处理Ruby Procs的例子中都有以下&符号。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

括号之前,不要把一个空间! :) – 2015-02-10 19:11:27

+0

对不起,来自JavaScript世界的习惯:)我会在我的例子中纠正它。 – wmock 2015-02-10 19:12:45

+0

http://ablogaboutcode.com/2012/01/04/the-ampersand-operator-in-ruby/ – user2864740 2015-02-10 19:32:09

回答

29

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 

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

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" 

,如果你申报“块”作为一个简单的参数(因此无符号),这些块相关的功能将不可用过去了,因为这只是一个非常方法的论点。

+0

感谢您的彻底回应和示例 - 真正帮助我更好地理解这一点! – wmock 2015-02-10 19:32:20

+0

我发现链接的文章令人沮丧和不愉快的阅读。谢谢你的替换。 – Hovis 2016-03-28 01:53:37

3

那么,当你有一个,如果块之前申请&,它成为Proc对象,反之亦然。

_unary &_:它与转换块之间的东西有关。如果你没有采取任何其他措施,请记住,当你在Ruby中看到一个单一的“&”时,你正在考虑把某些东西变成块,或者把某个东西变成块。

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

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

+0

就这样我清楚了,你是说如果我有一个Proc对象并且用&前缀它,结果可以用作方法的代码块吗?另一方面,如果我有一个代码块,然后我用&添加它,我可以使用结果作为proc?谢谢! – wmock 2015-02-10 19:12:03

+2

只有两个地方可以使用一元前缀'&'操作符:参数列表和参数列表。在参数列表中,它的意思是“将正在传递的块转换为Proc并将其绑定到名称”。在一个参数列表中,它意味着“将Proc转换为一个块,就好像它已经作为一个文字块一样传递了”,另外还有一个好处,如果被传递的对象不是'Proc',Ruby将首先调用' to_proc'将它强制转换为'Proc',这可以使'Symbol#to_proc'等整洁的技巧成为可能。 – 2015-02-11 00:57:27

2

不同的是,在你的第一个例子:

# 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()的方法定义内调用的方法调用后指定的块:

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

+0

感谢您的说明示例!真的有帮助! – wmock 2015-02-10 20:34:26

6

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

要转换blockProc

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