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

发布于 2025-01-10 04:08:16 字数 340 浏览 0 评论 0原文

这只是一个简单的问题,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

It's just a simple question, how is y.<< method is able to halt the code-block mid execution ??

I have expected the code block to run only once and never halt in the middle :/

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

甜点 2025-01-17 04:08:16

几乎所有 Ruby 实现都是免费软件和开源的,因此您只需查看源代码即可了解它是如何实现的。

Rubinius 中,最有趣的部分是 Enumerator::Iterator#reset,实现于 < a href="https://github.com/rubinius/rubinius/blob/master/core/enumerator.rb#L491-L498" rel="nofollow noreferrer">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 的实现非常相似,您可以在 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 也非常相似,正如您在 核心/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 的实现非常相似,您可以在 mrbgems/mruby-enumerator/mrblib/enumerator.rb

YARV 也使用 Fiber,如 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 是使用 许多 Ruby 实现中的FiberFiber 本质上只是 Ruby 的半协程的名称,当然,协程是实现 生成器迭代器。例如,CPython 和 CoreCLR 也使用协程实现生成器。

一个例外似乎是 Opal。我的假设是 Opal 将使用 ECMAScript 生成器实现 Ruby Enumerator,但看起来情况并非如此。 Opal 中 Ruby Enumerator 的实现可在 opal/corelib/enumerator.rb, opal/corelib/enumerator/generator.rbopal/corelib/enumerator/yielder.rbopal/corelib/runtime.js,但不幸的是,我并不完全理解它。不过,它似乎没有使用 Ruby Fiber 或 ECMAScript 生成器

顺便说一句,您对 Enumerator 的使用有点奇怪:您调用 Enumerator#each 六次(不带块),但调用 Enumerator#each 不带块只会返回 Enumerator本身:

每个枚举

根据此Enumerator 的构造方式迭代块。如果没有块并且没有给出参数,则返回 self。

因此,换句话说,所有对 Enumerator#each 的调用都只是无操作。调用 Enumerator#next< 会更有意义/code>直接:

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

Almost all Ruby implementations are Free Software and Open Source, so you can just look at the source code to see how it is implemented.

In 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

and 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, for example here:

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;
}

So, not surprisingly, 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. E.g. CPython and CoreCLR also implement generators using coroutines.

One exception to this seems to be 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, but unfortunately, I don't fully understand it. It does not appear to use either Ruby Fibers or ECMAScript Generators, though.

By the way, your usage of Enumerators is somewhat strange: you call Enumerator#each six times without a block, but calling Enumerator#each without a block just returns the Enumerator itself:

eachenum

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

So, in other words, all those calls to Enumerator#each are just no-ops. It would make much more sense to just call Enumerator#next directly:

puts e.next
puts e.next
puts e.next
e.rewind
puts e.next
puts e.next
puts e.next
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文