モンキーパッチでArray#eachを上書き(ついでにHash#eachも

Array#eachからRubyのブロックを理解する - Qiita
へのコメントの備忘録。

実際にyieldを使ってeachメソッドを作ってみると、次のようになります。(eachメソッドを上書きしようとしたらstack level too deep (SystemStackError)になってしまいましたので、each2という名前にしています。)

class Array
  def each2
    for i in self
      yield i
    end
  end
end

[1,2,3].each2 do |i|
  puts i
end

これに対して。



実装を見ると for を使っておられますが、 Ruby において for の実装は each で行われているはずです。結果として each メソッドの定義に each メソッドを使ったことになり、 stack level too deep (SystemStackError) が生じると思われます。
ですので for を使わずに

class Array
  def each
    self.size.times do |i|
      yield self[i]
    end
  end
end

とすれば良いようです。



コメントは以上です。
本当は block_given? 付けなきゃいけないのですが手抜きバージョンのままにしてあります。 def each 直後に

return to_enum(__method__) unless block_given?

を入れればいいと思います(以下同様)。
参考:Ruby - ブロックを与えない場合にEnumeratorを返すメソッドを作る - Qiita

なお、実は self を抜いて以下のように書いても OK です。

class Array
  def each
   size.times do |i|
      yield [i]
    end
  end
end

でも self あったほうが理解しやすいですよね。



ついでに Hash#each も書いてみました。 self 付けてますが、やはり無くてもいけます。

class Hash
  def each
    ary = self.to_a
    self.size.times do |i|
      yield ary[i].first, ary[i].last
    end
  end
end



おまけ。
プログラミング言語Ruby」p.262から引用。 next メソッドを持つモジュール(ここでは Iterable と名付けている)において each メソッドを定義する方法が載ってます。本来は Module#include に関係する記事。

module Iterable
  include Enumerable
  def each
    loop do
      yield self.next
    end
  end
end

each メソッドを持っているモジュール/クラスに include Enumerable してやると、Enumerable モジュールのメソッドが全て使えるようになります。とても便利です。

ちなみに Kernel.#loop が使っても

与えられたブロック内で StopIteration を Kernel.#raise すると ループを終了して nil を返します。
module function Kernel.#loop

ということを前提にして

列挙が既に最後へ到達している場合は、 StopIteration 例外を発生します。
instance method Enumerator#next

という Enumerator#next メソッドに倣った Iterable#next メソッドを定義しておけば、永久ループにならずコレクションの最後でループが終了します。

プログラミング言語 Ruby

プログラミング言語 Ruby

ちなみに「Ruby逆引きハンドブック」p.696によると、StopIteration 例外で Kernel.#loop を抜けることが出来るようになったのは1.8.7以降だそうです。
Ruby逆引きハンドブック

Ruby逆引きハンドブック