Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enumerator block execution order in Ruby

Tags:

ruby

In David Black's The Well-Grounded Rubyist I came across the following Ruby code about enumerators:

e = Enumerator.new do |y|
    puts "Starting up the block!"
    (1..3).each {|i| y << i }
    puts "Exiting the block!" 
end

p e.to_a

which returns the following output:

Starting up the block!
Exiting the block!
[1, 2, 3]

What bothers me the most is that I can't wrap my head around the order of execution. I believe the output should have been the more straightforward:

Starting up the block!
[1, 2, 3]
Exiting the block!

Any help would be much appreciated.

like image 453
stratis Avatar asked Jan 14 '17 18:01

stratis


3 Answers

e.to_a

If you want to create an Array out of your Enumerator, every element must first be extracted out of it!

Ruby needs to make sure that nothing is forgotten, and needs to execute the whole Block before returning an Array. There could be another line which defines more elements :

e = Enumerator.new do |y|
    puts "Starting up the block!"
    (1..3).each {|i| y << i }
    puts "Exiting the block! (Not really)"
    (4..6).each {|i| y << i }
    puts "Exiting the block!"
end

It outputs :

Starting up the block!
Exiting the block! (Not really)
Exiting the block!
[1, 2, 3, 4, 5, 6]

Alternatives

As alternatives, you could use :

e.next

p e.next
p e.next
p e.next
p e.next

It outputs :

Starting up the block!
1
2
3
Exiting the block!
enum.rb:11:in `next': iteration reached an end (StopIteration)

e.each

e.each do |x|
  puts x
end

#=>
# Starting up the block!
# 1
# 2
# 3
# Exiting the block!

e.map

If you want to create an Array but still see the correct execution order, you can use :

p e.map{ |x|
  p x
}

It outputs :

Starting up the block!
1
2
3
Exiting the block!
[1, 2, 3]
like image 131
Eric Duminil Avatar answered Oct 23 '22 11:10

Eric Duminil


You are surprised about this output.

Starting up the block!
Exiting the block!
[1, 2, 3]

It's very simple. Comments will illustrate what's going on.

e = Enumerator.new do |y|
    # print first message
    puts "Starting up the block!"

    # append elements to array y (but don't print it)
    (1..3).each {|i| y << i }

    # print second message
    puts "Exiting the block!" 
end

# print the array
p e.to_a
like image 25
Marko Avlijaš Avatar answered Oct 23 '22 13:10

Marko Avlijaš


I will slightly refactor the last line so maybe it's a bit more clear:

e = Enumerator.new do |y|
    puts "Starting up the block!"
    (1..3).each {|i| y << i }
    puts "Exiting the block!" 
end

a = e.to_a

# At this point "Starting up the block!" and "Exiting the block!" 
# have been printed.
# Also the value of a is [1, 2, 3].

puts a # Prints [1, 2, 3]

The code above is equivalent to the one in your question. What is going on is that the array is not printed by the Enumerator. The last line is the one who actually prints its value.

When e.to_a is executed, the Enumerator block will run. It will print the start message, add the values into the y array and print the exit messages. When it finished, the a variable will contain [1, 2, 3] and the last puts will finally print it.

Hopefully this makes a bit more sense.

like image 42
phss Avatar answered Oct 23 '22 12:10

phss