2
3

More than 5 years have passed since last update.

Ruby の Array#each を再定義しても Array インスタンスの Enumerable のメソッドはその each を使わない場合がある

Last updated at Posted at 2017-09-05

Ruby のEnumerable のメソッドはミックスイン先のeachメソッドを使用するが、 Arrayeach を再定義したとき、 Array のインスタンスメソッドは再定義した each を使わない場合がある。

Array#each を再定義する

再定義する

class Array
  def each
    count = 0
    while self[count]
      puts 'here!'
      yield self[count]
      count += 1
    end
    self
  end
end

ar = [1, 2, 3, 4, 5]

ar.each{|v| puts v}
=begin
出力は以下の通り
here!
1
here!
2
here!
3
here!
4
here!
5
=end


m = ar.map{|v| v * 2} # => "here!"が出力されない
p m # => [2, 4, 6, 8, 10]

特異メソッドで上書きする

ar = [5,2,3,4,1]

def ar.each
  puts 'here!'
  dupped = self.dup
  while item = dupped.shift
    yield item
  end
  self
end

# 再定義した時と同様に"here!"は出力されない
puts ar.map {|i| i * 2}

継承したクラス

class AnotherArray < Array
  def each
    d = self.dup
    while item = d.shift
      puts 'here!'
      yield item
    end
    self
  end
end

ar = AnotherArray.new [1,2,3,4,5]
ar.each{|v| puts v} #=> "here!"が出力される
m = ar.map{|v| v*v} #=> "here!"が出力されない
p m #=> [1, 4, 9, 16, 25]

自分で定義した Enumerable なクラスの場合は上書きされる

もちろん、自分で定義した Enumerable なクラスの場合は each を上書きすると上書きしたものが使われる

ruby:another_array.rb
class AnotherArray
  include Enumerable
  def initialize src
    @src = src
  end
  def each
    puts 'AnotherArray#each'
    @src.each {|item| yield item}
  end
end

ar = AnotherArray.new [1, 2, 3, 4, 5] 
m = ar.map {|v| v * 2} # => "AnotherArray#each" が出力される
p m # => [2, 4, 6, 8, 10]

再定義する

class AnotherArray
  def each
    puts 'This each is broken!'
  end
end

ar = AnotherArray.new [1, 2, 3, 4, 5]
broken = ar.map {|v| v * 2} # => 'This each is broken!' が出力される
p broken # => []

特異メソッドで上書きする

ar = AnotherArray.new [1, 2, 3, 4, 5] 

def ar.each
  puts 'Fixed.'
  @src.each {|item| yield item}
end

fixed = ar.map {|v| v * 2} # => "Fixed." が出力される
p fixed # => [2, 4, 6, 8, 10]

継承したクラス

class ChildAnotherArray < AnotherArray
  def each
    puts 'not implemented!'
  end
end

child = ChildAnotherArray.new [1,2,3,4,5]
child.each{|v| puts v} #=> "not implemented!"
child.map{|v| v * 2} #=> "not implemented!"

追記

原因

コメントをいただきました@scivola さんありがとうございます。

原因は、 Array#mapEnumerable#map と処理系レベルで別実装になっているから。 なので、array.cで実装されていない他の Enumerable のメソッドでは再定義した each が使われれます。

class Array
  def each
    puts 'broken each'
  end
end

ar = [1, 2, 3, 4]
ar.map {|v|v * 2} # => "broken each" は出力されない
ar.member? 1 # => "broken each" が出力される

また、 mruby など処理系によっては Array#mapEnumerable#mapのものもあるようです。 上記のコードを mruby で実行するとどちらも "broken each" が出力されます。

2
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3