Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Suppose we want to print the first three triangular numbers. A naive implementation would be to use a function:</p> <pre><code>def print_triangular_numbers steps number = 0 count = 1 steps.times do number += count count += 1 print number, " " end end print_triangular_numbers(3) </code></pre> <p>The disadvantage here is that we're mixing the printing logic with the counting logic. If we don't want to print the numbers, this isn't useful. We can improve this by instead <em>yielding</em> the numbers to a block:</p> <pre><code>def triangular_numbers steps number = 0 count = 1 steps.times do number += count count += 1 yield number end end triangular_numbers(3) { |n| print n, " " } </code></pre> <p>Now suppose we want to print a few triangular numbers, do some other stuff, then continue printing them. Again, a naive solution:</p> <pre><code>def triangular_numbers steps, start = 0 number = 0 count = 1 (steps + start).times do number += count yield number if count &gt; start count += 1 end end triangular_numbers(4) { |n| print n, " " } # do other stuff triangular_numbers(3, 4) { |n| print n, " " } </code></pre> <p>This has the disadvantage that every time we want to resume printing triangular numbers, we need to start from scratch. Inefficient! What we need is a way to remember where we left off so that we can resume later. Variables with a proc make an easy solution:</p> <pre><code>number = 0 count = 1 triangular_numbers = proc do |&amp;blk| number += count count += 1 blk.call number end 4.times { triangular_numbers.call { |n| print n, " " } } # do other stuff 3.times { triangular_numbers.call { |n| print n, " " } } </code></pre> <p>But this is one step forward and two steps back. We can easily resume, but there's no encapsulation of the logic (we could accidentally change <code>number</code> and ruin everything!). What we <em>really</em> want is an <strong>object</strong> where we can store the state. This is exactly what <code>Enumerator</code> is for.</p> <pre><code>triangular_numbers = Enumerator.new do |yielder| number = 0 count = 1 loop do number += count count += 1 yielder.yield number end end 4.times { print triangular_numbers.next, " " } # do other stuff 3.times { print triangular_numbers.next, " " } </code></pre> <p>Since blocks are closures in Ruby, the <code>loop</code> remembers the state of <code>number</code> and <code>count</code> between calls. This is what makes it seem like the enumerator is running in parallel.</p> <p>Now we get to the yielder. Note that it replaces <code>blk.call number</code> from the previous example where we used a proc. <code>blk.call</code> worked, but it was inflexible. In Ruby, you don't always have to provide enumerators with blocks. Sometimes you just want to enumerate one step at a time or chain enumerators together, in those cases having your enumerator simply pass a value to a block is inconvenient. <code>Enumerator</code> makes enumerators much simpler to write by providing the agnostic <code>Enumerator::Yielder</code> interface. When you give a value to the yielder (<code>yielder.yield number</code> or <code>yielder &lt;&lt; number</code>), you're telling the enumerator "Whenever someone asks for the next value (be it in a block, with <code>next</code>, <code>each</code>, or passed directly to another enumerator), give them this." The <code>yield</code> keyword simply wouldn't cut it here because it is <em>only</em> for yielding values to blocks.</p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload