This is IT

技術、日常

Enumerableのinclude先には、eachの定義が必要 を理解したい

概要

Ruby幼稚園生の自分にとって、module Enumerableに書いてある、

インクルードするクラスには each が定義されていなければなりません。

が、まぁよくわからなかったので、もう少しだけ理解を深めたいと思い書いてみた。

そもそもEnumerableって何なのよ?

繰り返し処理を行うクラスに、includeされているモジュール。

概要で書いた通り、インクルード先のクラスにはeachが定義されている必要がある。

そのため、Enumerableをインクルードしている「Array」「Hash」「Range」クラスあたりでは、ちゃんとインスタンスメソッドとしてeachメソッドが存在する。

自分が定義したオリジナルのクラスでEnumalebleのメソッドを使いたかったら、そのオリジナルクラスでeachメソッドを定義しておかなければいけない、ということになる。

まだ中々イメージが湧きづらいので、軽く実験をしながら、オリジナルのクラスでもEnumerableメソッドを使えるようにしてみる。

① eachメソッドを定義しなくてもincludeできるのか?

結論:できる。インスタンスの作成もできる。

class Foo
  include Enumerable

  def hello
    puts 'hello'
  end
end


foo = Foo.new
foo.hello
#=>hello

もちろんこの段階でEnumerableのメソッドを使おうとすると、エラーが出てしまうので注意。

foo.map(&:upcase)
#=>:in `map': undefined method `each' for #<Foo:0x00007f6950995488> (NoMethodError)

② Fooクラスにeachメソッドを定義してみる

class Foo
  include Enumerable

  def initialize(array)
    @array = array
  end

  def each 
    @array.each{|i| yield i }
  end
end


foo = Foo.new([1, 2, 3, 4, 5])
foo.map{|i| i * 2}
#=> [2, 4, 6, 8, 10]

初期化の際に作成したインスタンス変数@arrayは、Arrayクラスなので、当然

 @array.each{|i| yield i }

のようにeachメソッドを扱うことができる。

③ eachメソッドの定義を変えてみる

class Foo
  include Enumerable

  def initialize(array)
    @array = array
  end

  def hello
    puts 'hello'
  end

  def each 
    @array.each{|i| yield i.to_s(2)}
  end
end

foo = Foo.new([1, 2, 3, 4, 5])
foo.sum('#')
#=> "#11011100101"

今回は、eachメソッドの定義の段階で、to_sメソッドを呼び出し、全て2進数にしたあとに、yieldメソッドでブロックに値を渡すようにした。

元々のeachメソッドの定義や、yield・Procオブジェクトをもっとうまく活用できれば、更に柔軟な設計が出来そうだ。