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