在 Ruby 中为 Enumerable mixin 实现 `each`

implement `each` for Enumerable mixin in Ruby

我正在 Ruby 学习 Enumerable 的魔力。我听说只需要包含 Enumerable 并实现 each 方法,就可以拥有 Enumerable 的功能 class.

所以,我考虑实现自己的自定义 class Foo 来练习。如下所示:

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

这个 class 接受一个数组,它的 eachArray#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]?

除最后一条声明外,一切都如我所料。我知道它的 return 值,但不应该是 [2, 3, 4]。有没有办法做到 [2, 3, 4].

也请评论我实现的方式each。如果有更好的方法请告诉我。起初在我的实现中我没有这条线 return enum_for(:each) unless block_given? 然后当没有提供块时它不工作。我从某处借用了这条线,还请告诉我这是否是正确的处理方式。

each 不修改数组。如果要return修改数组,使用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,因为您希望将它用于所有计算。此外,您可以通过在块中传递 block_given? 来修改 each 方法以避免使用 enum_for。最后您的代码将如下所示:

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]

您需要使用 map 而不是 each

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

Foo 包含 Enumerable 这一事实意味着 Enumerable 的所有方法都可以在 Foo

的实例上调用

This class takes an array and its each works almost similar to Array#each.

I know its the return value but shouldn't it be [2, 3, 4].

  1. A def returns 最后一条语句的执行结果。

  2. Array#each returns原数组

将这些规则应用到您的 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]

each 的 return 值应该是接收者,即 self。但是您正在 return 调用 @num.each 的结果。现在,正如我刚才所说,each returns self,因此 @num.each returns @num.

修复很简单:只需 return 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 也应该 return 一个 Enumerator 当调用时没有块,但你已经正确处理。提示:如果你想通过覆盖它们来实现其他一些 Enumerable 方法的优化版本,或者想添加你自己的 Enumerable-like 方法,具有与原始方法相似的行为,你将要去一遍又一遍地剪切和粘贴完全相同的代码行,有一次,您会不小心忘记更改方法的名称。如果用 return enum_for(__callee__) unless block_given? 替换该行,则不必记住更改名称。]