Voy a utilizar 1.9.2-p0, ya que es lo que tengo a mano.
La función rb_check_array_type
se parece a esto:
VALUE
rb_check_array_type(VALUE ary)
{
return rb_check_convert_type(ary, T_ARRAY, "Array", "to_ary");
}
Y rb_check_convert_type
se parece a esto:
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;
}
Nota convert_type
la llamada. Esto se parece mucho a la versión C de Array.try_convert
y try_convert
sólo pasa a tener este aspecto:
/*
* 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);
}
Así que, sí, el primer bucle está buscando cualquier cosa en argv
que no es una matriz y el establecimiento del indicador allary
si encuentra tal cosa
En enum.c
, vemos esto:
id_each = rb_intern("each");
Así id_each
es una referencia interna para el método iterador Rubí each
.Y en vm_eval.c
, tenemos esto:
/*!
* 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, ...)
Así que esto:
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
está llamando to_enum
(con, esencialmente, la default argument) sobre lo que está en argv[i]
.
Por lo tanto, el resultado final de los primeros bloques for
y if
es que argv
está lleno de matrices o lleno de enumeradores en lugar de ser una mezcla de los dos. Pero observe cómo funciona la lógica: si se encuentra algo que no es una matriz, todo se convierte en un enumerador. La primera parte de la función enum_zip
envolverá matrices en enumeradores (que es esencialmente gratuita o al menos lo suficientemente barata como para no preocuparse) pero no expandirá los enumeradores en matrices (lo que podría ser bastante caro). Las versiones anteriores podrían haber ido para otro lado (prefieren matrices sobre enumeradores), lo dejo como un ejercicio para el lector o los historiadores.
La parte siguiente:
if (!rb_block_given_p()) {
result = rb_ary_new();
}
crea una matriz nueva vacío y lo deja en result
si zip
se está llamando sin un bloque. Y aquí hay que señalar lo zip
returns:
enum.zip(arg, ...) → an_array_of_array
enum.zip(arg, ...) {|arr| block } → nil
Si hay un bloque, entonces no hay nada para volver y result
puede permanecer como Qnil
; si no hay un bloque, entonces necesitamos una matriz en result
para que se pueda devolver una matriz.
De parse.c
, vemos que NODE_DOT2
es un rango de puntos dobles pero parece que solo están usando el nuevo nodo como una simple estructura de tres elementos; rb_new_node
simplemente asigna un objeto, establece algunos bits, y asigna tres valores en un struct:
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
es sólo una macro poco jugando. Ahora tenemos memo
como una estructura de tres elementos. Este uso de NODE_DOT2
parece ser un inconveniente conveniente.
La función rb_block_call
parece ser el iterador interno central. Y vemos a nuestro amigo id_each
nuevamente, así que haremos una iteración each
. Entonces vemos una elección entre zip_i
y zip_ary
; aquí es donde las matrices internas se crean y se presionan en result
. La única diferencia entre zip_i
y zip_ary
parece ser la gestión de excepciones StopIteration en zip_i
.
En este punto nos hemos hecho la compresión y que o bien tener la matriz de matrices en result
(si no había bloque) o tenemos Qnil
en result
(si había un bloque).
Resumen Ejecutivo: El primer bucle explícitamente Evita expansión enumeradores en matrices. Las llamadas zip_i
y zip_ary
solo funcionarán con matrices no temporales si tienen que crear una matriz de matrices como valor de retorno.Por lo tanto, si llama al zip
con al menos un enumerador que no es de matriz y utiliza el formulario de bloque, entonces son los enumeradores hasta el final y el "problema con el zip es que crea matrices internamente" no sucede. La revisión de 1.8 u otras implementaciones de Ruby se deja como un ejercicio para el lector.
"Si no hay bloque, no hay nada que devolver y el resultado puede seguir siendo Qnil", ¿quiere decir "si hay un bloque"? –
@Andrew: Correcto, corregido. Estaba escribiendo esto a las 05:00 después de todo :) –
¿Qué significa "memo"? ¿Es corto para la memorización? –