Ruby 枚举和 RETURN_ENUMERATOR - 有关 Ruby C 内部的问题
我对 Ruby 如何处理枚举器的创建有点困惑。基于块的迭代很有意义并且对我有用;我仍然很困惑枚举器的返回应该如何按代码方式运行。
这是我正在使用的代码:
VALUE rb_RPRuby_Sender_Kernel_each_backtrace_frame( int argc,
VALUE* args,
VALUE rb_self ) {
rb_thread_t* c_thread = GET_THREAD();
// Get the current frame - we're doing a backtrace, so our current working frame to start is the first previous thread
rb_control_frame_t* c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( RUBY_VM_PREVIOUS_CONTROL_FRAME( c_thread->cfp ) );
// c_top_of_control_frame describes the top edge of the stack trace
// set c_top_of_control_frame to the first frame in <main>
rb_control_frame_t* c_top_of_control_frame = RUBY_VM_NEXT_CONTROL_FRAME( RUBY_VM_NEXT_CONTROL_FRAME( (void *)( c_thread->stack + c_thread->stack_size ) ) );
// for each control frame:
while ( c_current_context_frame < c_top_of_control_frame ) {
VALUE rb_frame_hash = rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame( & c_current_context_frame );
// if we don't have a block, return enumerator
RETURN_ENUMERATOR( rb_self, 0, NULL );
// otherwise, yield the block
rb_yield( rb_frame_hash );
c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( c_current_context_frame );
}
return Qnil;
}
在枚举器的情况下,如何调用 while 循环中的最后一行?
我的所有循环活动是否都必须在调用 RETURN_ENUMERATOR 之前发生(因为 RETURN_ENUMERATOR 大概必须在 rb_yield() 之前出现)?
如果我希望内部迭代完成后发生一些事情怎么办?有了这个块,我可以简单地将它放在 while 循环之后;大概在枚举器的情况下也能起到同样的作用——但是如何呢?似乎每次循环都会返回一个枚举器,那么枚举器如何知道返回适当的对应对象呢? rb_yield 获取 rb_frame_hash 作为传递的参数,但 RETURN_ENUMERATOR 似乎采用当枚举器在内部调用该方法时中继到该方法的参数。很明显,枚举器正在调用方法本身 - 也许使用某种内部块来简单地返回 rb_frame_hash 的实例?
任何对内部结构的洞察都会受到赞赏。
-亚瑟
I'm a bit confused about how Ruby handles the creation of Enumerators. Block-based iteration makes sense and is working for me; I am still confused how the return of an Enumerator is supposed to function code-wise.
Here is the code I am working with:
VALUE rb_RPRuby_Sender_Kernel_each_backtrace_frame( int argc,
VALUE* args,
VALUE rb_self ) {
rb_thread_t* c_thread = GET_THREAD();
// Get the current frame - we're doing a backtrace, so our current working frame to start is the first previous thread
rb_control_frame_t* c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( RUBY_VM_PREVIOUS_CONTROL_FRAME( c_thread->cfp ) );
// c_top_of_control_frame describes the top edge of the stack trace
// set c_top_of_control_frame to the first frame in <main>
rb_control_frame_t* c_top_of_control_frame = RUBY_VM_NEXT_CONTROL_FRAME( RUBY_VM_NEXT_CONTROL_FRAME( (void *)( c_thread->stack + c_thread->stack_size ) ) );
// for each control frame:
while ( c_current_context_frame < c_top_of_control_frame ) {
VALUE rb_frame_hash = rb_RPRuby_Sender_Kernel_internal_backtraceHashForControlFrame( & c_current_context_frame );
// if we don't have a block, return enumerator
RETURN_ENUMERATOR( rb_self, 0, NULL );
// otherwise, yield the block
rb_yield( rb_frame_hash );
c_current_context_frame = RUBY_VM_PREVIOUS_CONTROL_FRAME( c_current_context_frame );
}
return Qnil;
}
How would the final line in the while loop be called in the case of an Enumerator?
Does all of my loop activity have to take place before calls to RETURN_ENUMERATOR (since RETURN_ENUMERATOR presumably has to come before rb_yield())?
What if I want something to happen once the internal iteration finishes? With the block I can simply put it after the while loop; presumably the same works in the case of an Enumerator- but how? It seems like every time through the loop it returns an Enumerator, so how does the Enumerator know to return the appropriate corresponding object? rb_yield gets rb_frame_hash as a passed arg, but RETURN_ENUMERATOR seems to take the args that are relayed to the method when the Enumerator calls the method internally. So clearly the Enumerator is calling the method itself- perhaps with some sort of internal block that simply returns the instance of rb_frame_hash?
Any insight into the internals is appreciated.
-Asher
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
尝试回答我自己的问题:
当调用 RETURN_ENUMERATOR 时,会调用 rb_enumeratorize,这会创建一个枚举器。返回枚举器;当在枚举器上调用 :next 时,光纤将被初始化(如果需要)或恢复。每次调用 :next 时,Fiber 都会迭代一次内部提供的块,以便获取下一个迭代器项(在枚举器的 C 结构中设置 no_next 并在枚举器的 Fiber 上调用 rb_ Fiber_yield )。
因此,循环活动似乎不必在 RETURN_ENUMERATOR 之前发生。我还不清楚在未提供块的情况下返回枚举器的函数中枚举后的操作。
To attempt to answer my own question:
When RETURN_ENUMERATOR is called, rb_enumeratorize is called, which creates an Enumerator. The Enumerator is returned; when :next is called on the Enumerator, a Fiber is initialized (if necessary) or resumed. Each time :next is called, the Fiber iterates an internally provided block once in order to get the next iterator item (setting no_next in the Enumerator's C struct and calling rb_fiber_yield on the Enumerator's Fiber).
So it would seem that the loop activity does not have to take place before RETURN_ENUMERATOR. I'm not yet clear on actions after enumeration in the function returning an Enumerator in the case that a block was not provided.