使用代码块创建的枚举器如何实际运行
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
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 Fiber
s 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 Enumerator
s, but it does not look like that is the case. The implementation of Ruby Enumerator
s 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 Fiber
s 或 ECMAScript Generators。
顺便说一下,您对 Enumerator
s 的用法有点奇怪:您调用 Enumerator#each
六次而没有阻塞,但是调用 Enumerator#each
却没有阻塞 returns Enumerator
本身:
each
→ enum
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
这只是一个简单的问题,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
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 Fiber
s 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 Enumerator
s, but it does not look like that is the case. The implementation of Ruby Enumerator
s 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 Fiber
s 或 ECMAScript Generators。
顺便说一下,您对 Enumerator
s 的用法有点奇怪:您调用 Enumerator#each
六次而没有阻塞,但是调用 Enumerator#each
却没有阻塞 returns Enumerator
本身:
each
→enum
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