首页 > 解决方案 > 在这个 for/each 语句中,为什么我需要在我正在迭代的对象的类中使用 &block 指定一个 each 方法?

问题描述

这个标题不是最好的,所以我将在这里进一步解释。

我有一个 Card 类和一个 Deck 类,在 Main 中,我正在创建一个 Deck of Cards,然后打印出 Deck 中的所有卡片。我为 Card 制作了一个 to_string 方法(如下):

def to_string
 puts "#{self.rank} of #{self.suit}"
end

然后使用它和 Main 中的 for/each 语句打印 Deck 中的所有卡片:

for card in deck
  puts card.to_string
end

但是我收到一个错误,说#Deck 有一个“未定义的方法'each':(NoMethodError)。我做了一些搜索,发现解决方案是将这个方法添加到我的Deck 类中:

def each(&block)
 @deck.each(&block)
end

我确实理解(或者,我想我知道)它是如何.each工作的,因为我用它来为我的 Deck 创建所有 Card 对象——它将遍历一个数组并依次获取每个数组项。所以for card in deck基本上是有道理的deck.each。但我不确定&block这里在做什么。我对什么是块进行了一些研究(据我了解,基本上是“匿名代码”——例如,array.each语句中的指令。一种不是正式书面方法的方法)但我仍然不知道是什么&block本身就可以。

标签: rubyclassblock

解决方案


Ruby 中的每个方法都可以(但不是必须)采用可选的块参数,&在参数名称前用 & 符号表示。当方法被调用时,这个参数被赋予一个显式块的值。

我们可以通过编写一个简单地返回其块的函数来明确地看到这种行为。

def foo(&block)
  p block
end

那么如果我们运行

> foo() # Note: No block argument
nil
> foo { 1 } # Note: Block provided
#<Proc:0x...>

因此,如果您将显式块传递给方法,它将作为Proc对象传递给方法。这是如何.each工作的。线

[1, 2, 3].each { |x| puts x }

调用each[1, 2, 3]传递其call方法运行的块参数puts x。你可以认为它类似于

[1, 2, 3].each(->(x) { puts x })

在这里,我们传递了一个恰好是 lambda 的常规参数。这等同于上述(块参数被视为特殊),但理论上我们可以实现each任何一种方式;块语法更方便。

正如您正确推测的那样,for循环脱糖到类似.each. 粗略地说,以下两个是等价的。

for i in [1, 2, 3]
  foo i
end

 

i = nil
[1, 2, 3].each do |i|
  foo i
end

请注意,for循环实际上会付出额外的努力来确保变量确实逃脱了循环范围,而.each直接使用会产生一个不会转义的仅限本地变量。出于这个原因,.each无论如何,它通常被认为在 Ruby 中更惯用,并且大多数 Ruby 代码通常会避开显式for循环。(IMO.each也更漂亮,更符合 Ruby 语法的所有其他方面)


推荐阅读