Я буду использовать 1.9.2-p0, поскольку это то, что у меня под рукой.
Функция rb_check_array_type
выглядит следующим образом:
VALUE
rb_check_array_type(VALUE ary)
{
return rb_check_convert_type(ary, T_ARRAY, "Array", "to_ary");
}
И rb_check_convert_type
выглядиткак это:
VALUE
rb_check_convert_type(VALUE val, int type, const char *tname, const char *method)
{
VALUE v;
/* always convert T_DATA */
if (TYPE(val) == type && type != T_DATA) return val;
v = convert_type(val, tname, method, FALSE);
if (NIL_P(v)) return Qnil;
if (TYPE(v) != type) {
const char *cname = rb_obj_classname(val);
rb_raise(rb_eTypeError, "can't convert %s to %s (%s#%s gives %s)",
cname, tname, cname, method, rb_obj_classname(v));
}
return v;
}
Обратите внимание на вызов convert_type
.Это очень похоже на C-версию Array.try_convert
и try_convert
просто выглядит так:
/*
* call-seq:
* Array.try_convert(obj) -> array or nil
*
* Try to convert <i>obj</i> into an array, using +to_ary+ method.
* Returns converted array or +nil+ if <i>obj</i> cannot be converted
* for any reason. This method can be used to check if an argument is an
* array.
*
* Array.try_convert([1]) #=> [1]
* Array.try_convert("1") #=> nil
*
* if tmp = Array.try_convert(arg)
* # the argument is an array
* elsif tmp = String.try_convert(arg)
* # the argument is a string
* end
*
*/
static VALUE
rb_ary_s_try_convert(VALUE dummy, VALUE ary)
{
return rb_check_array_type(ary);
}
Так что, да, первый цикл ищет что-то в argv
это не массив и установка флага allary
, если он найдет такую вещь.
В enum.c
мы видим это:
id_each = rb_intern("each");
Итак, id_each
- этовнутренняя ссылка на метод итератора Ruby each
.И в vm_eval.c
у нас есть это:
/*!
* Calls a method
* \param recv receiver of the method
* \param mid an ID that represents the name of the method
* \param n the number of arguments
* \param ... arbitrary number of method arguments
*
* \pre each of arguments after \a n must be a VALUE.
*/
VALUE
rb_funcall(VALUE recv, ID mid, int n, ...)
Итак, это:
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
Вызывает to_enum
(с, по сути, аргументом по умолчанию ) для всего, что находится в argv[i]
.
Итак, конечный результат первых блоков for
и if
состоит в том, что argv
либо полон массивов, либо полон перечислителей, а не может бытьсмесь двух.Но обратите внимание, как работает логика: если найдено что-то, что не является массивом, то все становится перечислителемПервая часть функции enum_zip
обернет массивы в перечислителях (которые по существу бесплатны или, по крайней мере, достаточно дешевы, чтобы о них не беспокоиться), но не расширит перечислители в массивы (которые могут быть довольно дорогими).Более ранние версии могли пойти другим путем (предпочитая массивы перед перечислителями), я оставлю это как упражнение для читателя или историков.
Следующая часть:
if (!rb_block_given_p()) {
result = rb_ary_new();
}
Создаетновый пустой массив и оставляет его в result
, если zip
вызывается без блока.И здесь мы должны отметить, что zip
возвращает :
enum.zip(arg, ...) → an_array_of_array
enum.zip(arg, ...) {|arr| block } → nil
Если есть блок, то возвращать нечего и result
может оставаться как Qnil
;если блока нет, то нам нужен массив в result
, чтобы можно было возвращать массив.
Начиная с parse.c
, мы видим, что NODE_DOT2
- это диапазон из двух точек, но онпохоже, они просто используют новый узел как простую трехэлементную структуру;rb_new_node
просто выделяет объект, устанавливает несколько битов и присваивает три значения в структуре:
NODE*
rb_node_newnode(enum node_type type, VALUE a0, VALUE a1, VALUE a2)
{
NODE *n = (NODE*)rb_newobj();
n->flags |= T_NODE;
nd_set_type(n, type);
n->u1.value = a0;
n->u2.value = a1;
n->u3.value = a2;
return n;
}
nd_set_type
- это просто битовый макрос.Теперь у нас есть memo
как структура из трех элементов.Такое использование NODE_DOT2
представляется удобным ключом.
Функция rb_block_call
является внутренним итератором ядра.И мы снова видим нашего друга id_each
, поэтому будем выполнять итерацию each
.Затем мы видим выбор между zip_i
и zip_ary
;это где внутренние массивы создаются и помещаются в result
.Единственная разница между zip_i
и zip_ary
, по-видимому, заключается в обработке исключений StopIteration в zip_i
.
На данный момент мы выполнили архивирование, и у нас либо есть массив массивов в result
(если не было блока) или у нас Qnil
в result
(если был блок).
Резюме : первый цикл явно избегает расширения перечислителейв массивы.Вызовы zip_i
и zip_ary
будут работать только с временными массивами, если они должны создать массив массивов в качестве возвращаемого значения.Таким образом, если вы вызываете zip
хотя бы с одним перечислителем, не являющимся массивом, и используете блочную форму, то это перечислители до конца, и «проблема с zip в том, что он создает массивы внутри» не возникает.Обзор 1.8 или других реализаций Ruby оставлен читателю в качестве упражнения.