Ruby Enumeration и RETURN_ENUMERATOR — вопросы о внутреннем устройстве Ruby C

Я немного смущен тем, как Ruby обрабатывает создание Enumerators. Итерация на основе блоков имеет смысл и работает для меня; Я все еще не понимаю, как возврат Enumerator должен функционировать с точки зрения кода.

Вот код, с которым я работаю:

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; предположительно то же самое работает и в случае с перечислителем, но как? Кажется, что каждый раз в цикле он возвращает Enumerator, так как же Enumerator знает, что нужно вернуть соответствующий соответствующий объект? rb_yield получает rb_frame_hash в качестве переданного аргумента, но RETURN_ENUMERATOR, кажется, принимает аргументы, которые передаются методу, когда Enumerator вызывает метод внутри себя. Итак, очевидно, что Enumerator вызывает сам метод — возможно, с каким-то внутренним блоком, который просто возвращает экземпляр rb_frame_hash?

Любое понимание внутренностей приветствуется.

-Ашер


person Asher    schedule 07.07.2010    source источник


Ответы (1)


Чтобы попытаться ответить на мой собственный вопрос:

Когда вызывается RETURN_ENUMERATOR, вызывается rb_enumeratorize, который создает Enumerator. Перечислитель возвращается; когда :next вызывается в Enumerator, Fiber инициализируется (при необходимости) или возобновляется. Каждый раз, когда вызывается :next, Fiber выполняет итерацию внутренне предоставленного блока один раз, чтобы получить следующий элемент итератора (устанавливая no_next в C-структуре Enumerator и вызывая rb_fiber_yield для Fiber Enumerator).

Таким образом, может показаться, что действие цикла не должно происходить до RETURN_ENUMERATOR. Мне еще не ясны действия после перечисления в функции, возвращающей Enumerator в случае, если блок не был предоставлен.

person Asher    schedule 08.07.2010