Będę używać 1.9.2-p0, ponieważ to jest to, co mam pod ręką.
Funkcja rb_check_array_type
wygląda następująco:
VALUE
rb_check_array_type(VALUE ary)
{
return rb_check_convert_type(ary, T_ARRAY, "Array", "to_ary");
}
I rb_check_convert_type
wygląda następująco:
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;
}
Uwaga wywołanie convert_type
. To wygląda trochę jak C wersją Array.try_convert
i try_convert
właśnie dzieje się wyglądać następująco:
/*
* 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);
}
Tak, tak, pierwsza pętla szuka czegoś w argv
że nie jest tablicą i ustawienie flagi allary
jeśli to znajduje coś takiego.
W enum.c
widzimy to:
id_each = rb_intern("each");
Więc id_each
jest wewnętrzną odniesienia dla metody iteracyjnej Ruby each
.Aw vm_eval.c
mamy to:
/*!
* 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, ...)
Więc to:
argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each));
dzwoni to_enum
(z, Zasadniczo default argument) na to, co jest w argv[i]
.
Końcowym wynikiem pierwszych bloków for
i if
jest to, że argv
jest albo pełne tablic, albo jest pełne modułów wyliczających, a nie może być mieszanką tych dwóch. Ale zwróć uwagę, jak działa logika: jeśli coś zostanie znalezione, a nie jest tablicą, wszystko staje się modułem wyliczającym. Pierwsza część funkcji enum_zip
będzie owijała tablice w modułach wyliczających (która jest zasadniczo darmowa lub przynajmniej na tyle tanie, aby się nie martwić), ale nie będzie rozszerzać modułów wyliczających na tablice (co może być dość kosztowne). Wcześniejsze wersje mogły pójść w drugą stronę (preferują tablice nad modułami wyliczającymi), zostawię to jako ćwiczenie dla czytelnika lub historyków.
Kolejna część:
if (!rb_block_given_p()) {
result = rb_ary_new();
}
Tworzy nową pustą tablicę i pozostawia go w result
jeśli zip
jest nazywany bez bloku. I tu należy zauważyć, co zip
returns:
enum.zip(arg, ...) → an_array_of_array
enum.zip(arg, ...) {|arr| block } → nil
Jeśli jest blok, to nie ma nic do powrotu i result
może pozostać jako Qnil
; jeśli nie ma bloku, potrzebujemy tablicy w result
, aby tablica mogła zostać zwrócona.
Od parse.c
widzimy, że NODE_DOT2
to zakres podwójnego dotyku, ale wygląda na to, że używają nowego węzła jako prostej struktury trzyelementowej; rb_new_node
tylko przydziela celu, ustawia kilka bitów i wyznacza trzy wartości w struktury:
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
tylko nieco bawi makro. Teraz mamy memo
jako tylko strukturę trzech elementów. To użycie NODE_DOT2
wydaje się być wygodnym kludge.
Funkcja rb_block_call
wydaje się być wewnętrznym iteratorem rdzenia. I znów widzimy naszego przyjaciela id_each
, więc będziemy wykonywać iterację each
. Następnie widzimy wybór między zip_i
i zip_ary
; to tutaj tworzone są wewnętrzne tablice i wypychane na result
. Jedyną różnicą między zip_i
i zip_ary
wydaje się być obsługa wyjątków StopIteration w zip_i
.
W tym momencie zrobiliśmy na skompresowanie i nie mamy tablicę tablic w result
(jeśli nie było bloku) lub mamy Qnil
w result
(jeśli nie było bloku).
Streszczenie: Pierwsza pętla wyraźnie unika rozszerza rachmistrzów do tablic. Wywołania zip_i
i zip_ary
będą działać tylko z nie-tymczasowymi tablicami, jeśli muszą zbudować tablicę tablic jako wartość zwracaną.Tak więc, jeśli wywołasz zip
z co najmniej jednym nielicznikowym modułem wyliczającym i korzystasz z formularza blokowego, oznacza to, że są to enumeratory do końca, a "problem z zipem polega na tym, że tworzy on tablice wewnętrznie". Przeglądanie wersji 1.8 lub innych implementacji Rubiego jest pozostawione ćwiczeniu dla czytelnika.
"Jeśli nie ma bloku, to nie ma nic do zwrócenia, a wynik może pozostać jako Qnil" - masz na myśli "Jeśli jest blok"? –
@Andrew: Dobrze, poprawione. Pisałem to o 5:00 po wszystkim :) –
Co oznacza "memo"? Czy to skrót od zapamiętywania? –