【问题标题】:In this for/each statement, why do I need to specify an each method using &block in the class of the object I am iterating through?在这个 for/each 语句中,为什么我需要在我正在迭代的对象的类中使用 &block 指定一个 each 方法?
【发布时间】:2020-11-19 16:20:10
【问题描述】:

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

我有一个 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 有一个“未定义的方法‘每个’:(NoMethodError)。我做了一些搜索,发现解决方案是将此方法添加到我的 Deck 类中:

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

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

【问题讨论】:

    标签: ruby class block


    【解决方案1】:

    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 }
    

    [1, 2, 3] 上调用each,传递一个块参数,其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 语法的所有其他方面)

    【讨论】:

      【解决方案2】:

      我不确定我是否能正确澄清这一点,只是尝试一下...
      实际上for item in deck 实际上是那些以前学习过一些过程语言(如 Pascal 或 C)的语法糖,并且等效于方法调用:deck.each { |item| ... }。花括号中的部分是block 或任意函数。所以,你的代码:

      for card in deck
         puts card
      

      实际上翻译为eachdeck对象的调用:

      deck.each { |card| puts card }
      

      这个方法实际上是做什么的——你定义它来收集一些东西,它应该一个接一个地传递它的项目给给定的block。在块内,我们可以将这些项目用作具有我们喜欢的名称的变量(例如card)。 您实际上可以忽略块中的项目,例如:

      deck.each { puts "Hello" }
      

      如果你的牌组有 52 张牌,你会看到 52 次“你好”。
      最有趣的是,仅定义了 each 方法后,您实际上可以拥有许多其他方法,例如 count、min、max、map 等。为此,您只需要在类中包含 mixin Enumerable .
      像这样:

      class Deck
        include Enumerable
      
        def each(&block)
          @deck.each(&block)
      

      现在,如果您调用 deck.count,它实际上会返回 52,或者无论是多少张卡片,您都已经放在那里了。
      它是如何工作的:它实际上执行如下操作:

      c = 0
      deck.each { c = c + 1 }
      c
      

      嗯,希望对你有帮助……

      【讨论】:

        【解决方案3】:

        为什么我需要在我正在迭代的对象的类中使用&amp;block 指定each 方法?

        您不必使用&amp;block 语法。

        each 应该为给定块产生每个元素,例如:

        class Foo
          def each
            yield 1
            yield 2
            yield 3
          end
        end
        
        foo = Foo.new
        
        for i in foo
          puts i
        end
        
        # or more idiomatic:
        
        foo.each do |i|
          puts i
        end
        

        由于您很少产生硬编码值,因此each 中通常存在某种循环,例如:

        class Foo
          def each
            1.upto(3) do |i|
              yield i
            end
          end
        end
        

        在您的情况下,循环基于@deck.each,因此您可以编写:

        def each
          @deck.each do |card|
            yield card
          end
        end
        

        为了使each 更有用,如果没有给出块,它还应该返回一个枚举器:

        def each
          return enum_for(:each) unless block_given?
        
          @deck.each do |card|
            yield card
          end
        end
        

        那么&amp;block 语法呢?

        如果您阅读Array#each 的文档,您可能会注意到它已经完成了所有这些事情——它将元素生成到块中,如果没有给出块,则返回一个枚举器。因此,您可以避免编写上述代码,只需将提供给 您的 each 的块传递给 Array#each

        但是我们如何引用给定的块呢?这就是&amp;block 语法的用途——它将块分配给一个变量 block,它可以作为block argument 传递给使用相同&amp;block 语法的另一个方法:

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

        一些注意事项:

        • 你应该避免for,而更喜欢each风格的循环

        • to_string 应该只是 return 字符串,而不是打印它。你可以省略self,你应该叫它to_s

          def to_s
            "#{rank} of #{suit}"
          end
          

          允许您直接puts您的卡:

          deck.each do |card|
            puts card
          end
          
        • 调用each_card方法可能更简洁

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-07-11
          • 2015-11-30
          • 2020-03-23
          • 1970-01-01
          • 2013-05-20
          • 1970-01-01
          • 2011-04-24
          相关资源
          最近更新 更多