Home > Software engineering >  How does the enumerable module works by just adding an each method on a class in Ruby?
How does the enumerable module works by just adding an each method on a class in Ruby?

Time:08-21

I'm learning ruby and while studding modules I found that for making Enumerable module methods accessible inside a class you should also provide a each method inside your class. I don't really understand why doing this is making enumerable methods work.

Also, i don't understand why i don't need to call the each method please be kind I'm just beginning to learn

class List 
  include Enumerable
  
  def initialize (*list)
    @list = *list
  end

  def each(&block)
    @list.each(&block)
  end
end

Mylist = List.new(1,2,3,4,5)

puts Mylist.any? {|num| num>2} 

I'm planning to create my own Enumerable module whit some of the Enumerable methods and I think understanding this concept will help me on doing it.

CodePudding user response:

Let's first define instances of some classes that include the Enumerable module.

require 'set'

arr = [1, 2, 3]
hsh = { 1=>2, 2=>3, 3=>4 }
rng = (1..3)
set = (1..3).to_set

Suppose an enumerable method is defined as follows.

module Enumerable
  def reverse_em
    a = []
    each { |e| a.unshift(e) }
    a
  end
end

We may then write:

arr.reverse_em #=> [3, 2, 1]
hsh.reverse_em #=> [[:c, 3], [:b, 2], [:a, 1]]
rng.reverse_em #=> [3, 2, 1]
set.reverse_em #=> [3, 2, 1]

As you see, a single Enumerable method is used by instances of four different classes. This obviously is a superior design construct to defining reverse_em for each of the four classes.

As each in each { |e| a.unshift(e) } has no explicit receiver the implicit receiver is used. In [1, 2, 3].reverse_em, for example, the implicit receiver is [1, 2, 3] so each { |e| a.unshift(e) } evaluates to [1, 2, 3].each { |e| a.unshift(e) } with each being an instance method of the class Array.

To implement this functionality each of the class definitions for Array, Hash, Range and Set (and other core classes) must have a statement include Enumerable and must contain an instance method each that returns an enumerator (Array#each, Hash#each, Range#each and Set#each).

Note that reverse_em returns an array regardless of the receiver's class. That is the case with many if not all Emumerable methods.

If follows that a user's custom class can make use of all of the methods in the Enumerable module by including in the class definition the statement include Enumerable and an instance method each that returns an enumerator. Note that a few Enumerable methods depend on methods other than each that are to be provided by the class.


Now let's take a second example where the Enumerable method must account for the possibility that a block is passed. Specifically, let's consider how Enumerable#map might be coded.

module Enumerable
  def my_map
    return each unless block_given?
    a = []
    each { |e| a << yield(e) }
    a
  end
end

Firstly, if no block is passed an enumerator is returned that simply enumerates the elements of the receiver (as with Enumerable#map).

arr.my_map #=> #<Enumerator: [1, 2, 3]:each>
hsh.my_map #=> #<Enumerator: {1=>2, 2=>3, 3=>4}:each>
rng.my_map #=> #<Enumerator: 1..3:each>
set.my_map #=> #<Enumerator: #<Set: {1, 2, 3}>:each>

Now suppose a block is passed.

arr.my_map { |e| 2*e }   #=> [2, 4, 6]
hsh.my_map { |k,v| k*v } #=> [2, 6, 12]
rng.my_map { |n| n**2 }  #=> [1, 4, 9]
set.my_map { |n| n**2 }  #=> [1, 4, 9]   

CodePudding user response:

Module Enumerable is designed such way

Every Enumerable method uses each method which yields successive members of the collection. That's why you just need implement it and include Enumerable in your class

If you will use max, min, or sort methods, you also need to implement <=> method

  • Related