使用代码块创建的枚举器如何实际运行

How does enumerators created with code block actually runs

这只是一个简单的问题,y.<< 方法如何能够在代码块执行过程中停止??

我预计代码块 运行 只有一次并且永远不会在中间停止 :/

e = Enumerator.new do |y|
    puts "Ruby"
    y << 1
    y << 2
    puts "Ruby"
    y << 3
end

puts e.each.next
puts e.each.next
puts e.each.next
e.rewind
puts e.each.next
puts e.each.next
puts e.each.next

几乎所有 Ruby 实现都是自由软件和开源,因此您只需查看源代码即可了解其实现方式。

Rubinius, the most interesting part is Enumerator::Iterator#reset, implemented in core/enumerator.rb中:

@fiber = Fiber.new stack_size: STACK_SIZE do
  obj = @object
  @result = obj.each { |*val| Fiber.yield *val }
  @done = true
end

Enumerator::Iterator#next:

val = @fiber.resume

TruffleRuby's implementation is very similar, as you can see in src/main/ruby/truffleruby/core/enumerator.rb:

class FiberGenerator
  # irrelevant methods omitted

  def next
    reset unless @fiber

    val = @fiber.resume

    raise StopIteration, 'iteration has ended' if @done

    val
  end

  def reset
    @done = false
    @fiber = Fiber.new do
      obj = @object
      @result = obj.each do |*val|
        Fiber.yield(*val)
      end
      @done = true
    end
  end
end

JRuby is also very similar, as you can see in core/src/main/ruby/jruby/kernel/enumerator.rb:

class FiberGenerator
  # irrelevant methods omitted

  def next
    reset unless @fiber&.__alive__

    val = @fiber.resume

    raise StopIteration, 'iteration has ended' if @state.done

    val
  end

  def reset
    @state.done = false
    @state.result = nil
    @fiber = Fiber.new(&@state)
  end

end

MRuby's implementation is very similar, as you can see in mrbgems/mruby-enumerator/mrblib/enumerator.rb.

YARV also uses Fibers, as can be seen in enumerator.c,例如这里:

static void
next_init(VALUE obj, struct enumerator *e)
{
    VALUE curr = rb_fiber_current();
    e->dst = curr;
    e->fib = rb_fiber_new(next_i, obj);
    e->lookahead = Qundef;
}

static VALUE
get_next_values(VALUE obj, struct enumerator *e)
{
    VALUE curr, vs;

    if (e->stop_exc)
    rb_exc_raise(e->stop_exc);

    curr = rb_fiber_current();

    if (!e->fib || !rb_fiber_alive_p(e->fib)) {
    next_init(obj, e);
    }

    vs = rb_fiber_resume(e->fib, 1, &curr);
    if (e->stop_exc) {
    e->fib = 0;
    e->dst = Qnil;
    e->lookahead = Qundef;
    e->feedvalue = Qundef;
    rb_exc_raise(e->stop_exc);
    }
    return vs;
}

所以,毫不奇怪,Enumerator is implemented using Fibers in many Ruby implementations. Fiber is essentially just Ruby's name for semi-coroutines, and of course, coroutines are a popular way of implementing generators and iterators。例如。 CPython 和 CoreCLR 也使用协程实现生成器。

一个例外似乎是 Opal. My assumption was that Opal would use ECMAScript Generators to implement Ruby Enumerators, but it does not look like that is the case. The implementation of Ruby Enumerators in Opal is found in opal/corelib/enumerator.rb, opal/corelib/enumerator/generator.rb, and opal/corelib/enumerator/yielder.rb with some help from opal/corelib/runtime.js,但不幸的是,我并不完全理解它。不过,它似乎没有使用 Ruby Fibers 或 ECMAScript Generators

顺便说一下,您对 Enumerators 的用法有点奇怪:您调用 Enumerator#each 六次而没有阻塞,但是调用 Enumerator#each 却没有阻塞 returns Enumerator 本身:

eachenum

Iterates over the block according to how this Enumerator was constructed. If no block and no arguments are given, returns self.

所以,换句话说,所有对 Enumerator#each 的调用都只是 no-ops。直接调用 Enumerator#next 会更有意义:

puts e.next
puts e.next
puts e.next
e.rewind
puts e.next
puts e.next
puts e.next