the_prole the_prole - 1 month ago 6
Ruby Question

Why does chunk_while return Enumerator Class object

Why does chunk_while return Enumerator Class object

This code

array = [0, 1, 2, 3, 4, 5, 7, 8, 9, 15, 16]
p array.chunk_while {|i,j| i + 1 == j }


print this

#<Enumerator::Generator:0x00000002bef0a8>:each>


I have ruby version
ruby 2.3.1p112 (2016-04-26 revision 54768) [x64-mingw32]

Answer

Methods in the Enumerable module, such as chunk_while, require that receivers be enumerators, that is, instances of the class Enumerator. Therefore if an Enumerable method such as chunk_while returns an enumerator, it can be the receiver to another Enumerable method (and that method can be the receiver of another Enumerable method, and so on). This is called method chaining. That's why you see many Enumerable methods return an enumerator if no block is provided.

The chaining of methods that have enumerators as receivers may also include methods in other modules or in the class Enumerator, such as Enumerator#with_index.

This is why we can write expressions such as the following.

array.chunk_while {|i,j| i + 1 == j }.map.with_index { |a,i| i.even? ? a.reduce(:+) : 0 }
  #=> [15, 0, 31]

Let's break this down.

e0 = array.chunk_while {|i,j| i + 1 == j }
  #=> #<Enumerator: #<Enumerator::Generator:0x007fa01b9639e0>:each> 
e1 = e0.map
  #=> #<Enumerator: #<Enumerator: #<Enumerator::Generator:0x007fa01b9639e0>:each>:map> 
e2 = e1.with_index
  #=> #<Enumerator: #<Enumerator: #<Enumerator:
  #    #<Enumerator::Generator:0x007fa01b9639e0>:each>:map>:with_index> 
e2.each { |a,i| i.even? ? a.reduce(:+) : 0 }
  #=> [15, 0, 31] 

Examine the return values for the operations that produce e0, e1 and e2. e1 and e2 may be thought of as compound iterators.

As a matter of practice, chunk_while is almost always chained to another method, so it makes sense for it to return an enumerator.

You might well ask, "why must all enumerable methods require a receiver that is an enumerator, considering that chunk_while's receiver in the example, array, is not an enumerator"? The answer lies in the fact that every class that includes the Enumerable module must have a method each that returns an enumerator. One therefore could write

array.each.chunk_while {|i,j| i + 1 == j }.to_a

but Ruby saves you the trouble. Ruby will invoke Array#each for you when it sees that the method being invoked on an array requires an enumerator as its receiver. The same is true of all classes that have a method each.

Comments