Enumerable module
All methods in the module Enumerable
are instance methods that require their receiver to be an enumerator (an instance of the class Enumerator). All classes that include the module Enumerable
(using Module#include) must possess an instance method each
, which returns an enumerator. Three examples for built-in classes are Arrays#each, Hash#each and Range#each.1 To include Enumerable
in a custom class, therefore, one must define a method each
(that returns an enumerator) on that class.
When a method contained in Enumerable
is executed on an instance of a class that includes Enumerable
, Ruby inserts the method each
between the instance and the Enumerable
method.
For example, you can think of [1,2,3].map { |n| 2*n } #=> [2,4,6]
as [1,2,3].each.map { |n| 2*n }
(using Array#each
), { :a = 1, :b => 2 }.map { |k,v| v } #=> [1,2]
as { :a = 1, :b => 2 }.each.map { |k,v| v }
(using Hash#each
) and (1..4}.map { |n| 2*n } #=> [2,4,6]
as (1..4}.each.map { |n| 2*n }
(using Range#each
).
with_index
Now let's consider the method with_index
. Which modules (including classes, of course) have an instance method with_index
?
ObjectSpace.each_object(Module).select { |m| m.instance_methods.include?(:with_index) }
#=> [Enumerator::Lazy, Enumerator]
(where Enumerator::Lazy.superclass #=> Enumerator
)
See ObjectSpace#each_object.
It follows that invoking with_index
on any class other than these two will raise a no-method exception. That includes, for example, [1,2,3].with_index
and { :a=>1 }.with_index
. However, classes that have a method each
that returns an enumerator can make use of the method Enumerator#with_index by (explicitly) inserting each
between an instance of the class and with_index
.
For example2,
enum = [1,2,3].each.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:each>:with_index>
and then perhaps used thusly:
enum.to_a
#=> [[1, 0], [2, 1], [3, 2]]
This works because
[1,2,3].each.class
#=> Enumerator
so all instance methods of the class Enumerator
(including with_index
) can be invoked on the enumerator [1,2,3].each
.
1 A complete list is given by ObjectSpace.each_object(Class).select {|k| k.instance_methods.include?(:each)}
.
2 One would generally write enum = [1,2,3].each_with_index
(see Enumerable#each_with_index) rather than enum = [1,2,3].each.with_index
, but the latter is sometime used to make use of the fact that with_index
takes an argument (default 0) that specifies the index base. For example, [1,2,3].each.with_index(1).to_a #=> [[1, 1], [2, 2], [3, 3]]
.