2011-06-27 23 views
7

W Ruby - Compare two Enumerators elegantly, mówiłoCzy pliki ZIP z numerami całkowitymi w Ruby są tworzone wewnętrznie?

Problem z zamkiem jest to, że tworzy tablice wewnętrznie, bez względu co Enumerable zdać. Jest inny problem z długością wejścia params

musiałem spojrzeć na realizację przeliczalne # zamkiem w YARV i zobaczył

static VALUE 
enum_zip(int argc, VALUE *argv, VALUE obj) 
{ 
    int i; 
    ID conv; 
    NODE *memo; 
    VALUE result = Qnil; 
    VALUE args = rb_ary_new4(argc, argv); 
    int allary = TRUE; 

    argv = RARRAY_PTR(args); 
    for (i=0; i<argc; i++) { 
     VALUE ary = rb_check_array_type(argv[i]); 
     if (NIL_P(ary)) { 
      allary = FALSE; 
      break; 
     } 
     argv[i] = ary; 
    } 
    if (!allary) { 
     CONST_ID(conv, "to_enum"); 
     for (i=0; i<argc; i++) { 
      argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each)); 
     } 
    } 
    if (!rb_block_given_p()) { 
     result = rb_ary_new(); 
    } 
    /* use NODE_DOT2 as memo(v, v, -) */ 
    memo = rb_node_newnode(NODE_DOT2, result, args, 0); 
    rb_block_call(obj, id_each, 0, 0, allary ? zip_ary : zip_i, (VALUE)memo); 

    return result; 
} 

jestem zrozumienie następujące bity prawidłowo?

Sprawdź, czy wszystkie argumenty są tablicami, a jeśli tak, wymienić jakieś pośrednie odniesienie do tablicy z bezpośrednim odniesieniem

for (i=0; i<argc; i++) { 
     VALUE ary = rb_check_array_type(argv[i]); 
     if (NIL_P(ary)) { 
      allary = FALSE; 
      break; 
     } 
     argv[i] = ary; 
    } 

Jeśli nie są one wszystkie tablice utwórz wyliczający zamiast

if (!allary) { 
     CONST_ID(conv, "to_enum"); 
     for (i=0; i<argc; i++) { 
      argv[i] = rb_funcall(argv[i], conv, 1, ID2SYM(id_each)); 
     } 
    } 

Tworzenie tablicy tablic tylko wtedy, gdy blok nie jest podana

if (!rb_block_given_p()) { 
     result = rb_ary_new(); 
    } 

Jeśli wszystko jest tablicą, należy zip_ary, inaczej używać zip_i i wywołać blok na każdym zbiorze wartości

/* use NODE_DOT2 as memo(v, v, -) */ 
    memo = rb_node_newnode(NODE_DOT2, result, args, 0); 
    rb_block_call(obj, id_each, 0, 0, allary ? zip_ary : zip_i, (VALUE)memo); 

zwraca tablicę tablic jeśli nie blok zostanie podany inny zerową (Qnil)?

return result; 
} 

Odpowiedz

6

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.

+0

"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"? –

+1

@Andrew: Dobrze, poprawione. Pisałem to o 5:00 po wszystkim :) –

+0

Co oznacza "memo"? Czy to skrót od zapamiętywania? –

Powiązane problemy