I want to create an enumerator for external iteration via next that is clone-able, so that the clone retains the current enumeration state.
As an example, let's say I have a method that returns an enumerator which yields square numbers:
def square_numbers
  return enum_for(__method__) unless block_given?
  n = d = 1
  loop do
     yield n
     d += 2
     n += d
   end
end
square_numbers.take(10)
#=> [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
And I want to enumerate the first 5 square numbers, and for each value, print the subsequent 3 square numbers. Something that's trivial with each_cons:
square_numbers.take(8).each_cons(4) do |a, *rest|
  printf("%2d: %2d %2d %2d\n", a, *rest)
end
Output:
 1:  4  9 16
 4:  9 16 25
 9: 16 25 36
16: 25 36 49
25: 36 49 64
But unlike the above, I want to use external iteration using two nested loops along with next and clone:
outer_enum = square_numbers
5.times do
  i = outer_enum.next
  printf('%2d:', i)
  inner_enum = outer_enum.clone
  3.times do
    j = inner_enum.next
    printf(' %2d', j)
  end
  print("\n")
end
Unfortunately, the above attempt to clone raises a:
`initialize_copy': can't copy execution context (TypeError)
I understand that Ruby doesn't provide this out-of-the-box. But how can I implement it myself? How can I create an Enumerator that supports clone?
I assume that it's a matter of implementing initialize_copy and copying the two variable values for n and d, but I don't know how or where to do it.
Ruby fibers cannot be copied, and the C implementation of Enumerator stores a pointer to a fiber which does not appear to be exposed to Ruby code in any way.
https://github.com/ruby/ruby/blob/752041ca11c7e08dd14b8efe063df06114a9660f/enumerator.c#L505
if (ptr0->fib) {
    /* Fibers cannot be copied */
    rb_raise(rb_eTypeError, "can't copy execution context");
}
Looking through the C source, it's apparent that Enumerators and Fibers are connected in a pretty profound way. So  I doubt that there is any way to change the behavior of initialize_copy to permit clone.
Perhaps you could just write a class of your own that does what you ask:
class NumberSquarer
  def initialize
    @n = @d = 1
  end
  def next
    ret = @n
    @d += 2
    @n += @d
    ret
  end
end
ns1 = NumberSquarer.new
Array.new(5) { ns1.next }
# => [1, 4, 9, 16, 25]
ns2 = ns1.clone
Array.new(5) { ns2.next }
# => [36, 49, 64, 81, 100]
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With