在Ruby中为Enumerable mixin实现`each`

时间:2016-05-28 17:51:01

标签: ruby module block enumerable

我正在学习Ruby中Enumerable的神奇之处。我听说只需要包含each并实现class Foo include Enumerable def initialize numbers @num = numbers end def each return enum_for(:each) unless block_given? @num.each { |i| yield i + 1 } end end 方法,就可以拥有该类的Enumerable功能。

所以,我考虑过实现自己的自定义类Foo。它看起来如下:

each

此类采用数组,其Array#each的工作方式与>> f = Foo.new [1, 2, 3] => #<Foo:0x00000001632e40 @num=[1, 2, 3]> >> f.each { |i| p i } 2 3 4 => [1, 2, 3] # Why this? Why not [2, 3, 4]? 几乎相似。这是区别:

[2, 3, 4]

一切都像我期望的那样,除了一件事是最后一句话。我知道它的返回值,但不应该是[2, 3, 4]。有没有办法让它each

另外请评论我实施return enum_for(:each) unless block_given?的方式。如果有更好的方法请告诉我。在我的实现中,我没有使用这一行F = @(x, y, z)[x+y+z; x*y*z]; funcc = @(x)F(x(1), x(2), 0); ,然后在没有提供块的情况下它不起作用。我从某个地方借了这条线,也请告诉我这是否是处理这种情况的正确方法。

4 个答案:

答案 0 :(得分:2)

each不会修改数组。如果要返回修改后的数组,请使用map

def each
  return enum_for(:each) unless block_given?
  @num.map { |i| yield i + 1 }
end
f.each { |i| p i }
2
3
4
=> [2, 3, 4]

但我建议在自定义方法中使用每个方法。您可以在initialize方法中将数组的每个元素递增1,因为您希望将其用于所有计算。此外,您可以修改each方法,以避免在块中传递enum_for来使用block_given?。最后,您的代码将如下所示:

class Foo
  include Enumerable

  def initialize(numbers)
    @num = numbers.map {|n| n + 1 }
  end

  def each
    @num.each { |i| yield i if block_given? }
  end
end

f = Foo.new [1, 2, 3]
=> #<Foo:0x00000000f8e0d0 @num=[2, 3, 4]>
f.each { |i| p i }
2
3
4
=> [2, 3, 4]

答案 1 :(得分:2)

each的返回值应该是接收者,即self。但是你要回复调用@num.each的结果。现在,正如我刚才所说,each返回self,而错误@num.each会返回@num

修复很简单:只需返回self

def each
  return enum_for(:each) unless block_given?
  @num.each { |i| yield i + 1 }
  self
end

或者,可能还有一点Rubyish:

def each
  return enum_for(:each) unless block_given?
  tap { @num.each { |i| yield i + 1 }}
end

[实际上,从Ruby 1.8.7+开始,each在没有块的情况下调用时也应该返回Enumerator,但是你已经正确地处理了它。提示:如果您想通过覆盖它们来实现某些其他Enumerable方法的优化版本,或者想要添加自己的Enumerable类似行为的方法,那么就像原始方法一样一遍又一遍地剪切和粘贴完全相同的代码行,并且在某一时刻,您将意外忘记更改方法的名称。如果您使用return enum_for(__callee__) unless block_given?替换该行,则无需记住更改名称。]

答案 2 :(得分:1)

您需要使用map代替each

f.map { |i| p i }
#=> [2,3,4]

Foo包含Enumerable这一事实意味着可以在Enumerable

的实例上调用Foo的所有方法

答案 3 :(得分:0)

  

这个类采用一个数组,每个数组的工作方式与Array#each几乎相似。

     

我知道它的返回值,但不应该是[2,3,4]。

  1. def返回最后执行的语句的结果。

  2. Array#each返回原始数组。

  3. 将这些规则应用于您的def:

    def each
      return enum_for(:each) unless block_given?
      @num.each { |i| yield i + 1 }  #Array#each returns @num
    end  #When a block is given, the result of the last statement that was executed is @num
    

    您可以随时执行以下操作:

    class Foo
      include Enumerable
    
      def initialize numbers
        @num = numbers
        @enum_vals = []
      end
    
      def each
        if block_given?
          @num.each do |i| 
            yield i + 1
            @enum_vals << i + 1
          end
    
          @enum_vals
        else
          enum_for
        end
      end
    end
    
    result = Foo.new([1, 2, 3, ]).each {|i| p i}
    p result
    
    --output:--
    2
    3
    4
    [2, 3, 4]