Iteratory a pętle

Mam techniczne pytanko:

Dlaczego lepiej w Rubym korzystać z iteratorów takich jak each czy collect aniżeli ze zwykłej pętli for ?
Dlaczego lepiej napisać

[code]a = [1,2,3,4,5]

a.each { |e| puts e }[/code]
aniżeli

[code]a = [1,2,3,4,5]

for i in 1…a.size puts a[i][/code]
czy chodzi tylko o czytelność kodu i o to żeby ładnie wyglądał, czy o coś bardziej technicznego?

Czy taki iterator each nie jest właśnie zrealizowany pętlą for (z yield’em dla każdego elementu) ?

Drugi przykład zdecydowanie nie jest “Ruby Way”. Cytat z polskiego wydania Pickaxe (czyli de facto z dokumentacji):

Taki kod jest o niebo czytelniejszy niż na przykład iteracja po elementach tablicy w C++. I tak szczerze mówiąc, to zgadzam się z Batesem.

Chodzi o coś bardzo pragmatycznego. Kiedy chcesz przeiterować jakąś kolekcję to interesują Cię kolejne jej elementy. Dbanie o zmienną indeksacyjną wprowadza niepotrzebny bałagan. Poza tym nie wszystkie kolekcje (iteratory) muszą mieć dostęp po indeksie liczbowym (hashe, pliki). Nawet w javie dodano taki “for-each” (http://leepoint.net/notes-java/flow/loops/foreach.html) właśnie po to, żeby nie trzeba było ręcznie dbać o indeks. Mają przyzwyczajenia z takich języków jak C/C++ to może się wydawać wręcz idiotyczne, małostkowe, ale to tylko wrażenie.

Przeczytaj (na głos)

posts.each { |post| puts post }

A teraz:

for i in 1..a.size puts a[i]

Które zdanie brzmi lepiej?

Ja oczywiście zgadzam się, że taki kod z iteratorem jest bardziej czytelny. Interesowało mnie tylko czy jest tu coś głębszego, czy tylko o czytelność kodu chodzi. I chyba odpowiedź już dostałem :slight_smile:

Co do “głębszego” – jeśli będziesz tworzył własną klasę, która będzie Enumerable (czyli po prostu kolekcję), warto poczytać przynajmniej wstęp:
http://ruby-doc.org/core/classes/Enumerable.html

OK. Na pewno zajrzę…

Warto też wziąć pod uwagę kontekst. Poza each masz wiele innych metod, które akceptują blok i “coś tam robią z tablicą”. Taki kod jest po prostu bardziej spójny:

[code=ruby][1,2,3,4].select{|e| e % 2 ==0}.each{|e| puts e}

versus

tab = [1,2,3,4]
tab1 = []
for i in 1…tab.size
tab1 << tab[i] if tab[i] % 2 == 0
end
for i in 1…tab1.size
puts tab1[i]
end[/code]
Można powiedzieć, że to tylko pragmatyzm, ale ten pragmatyzm sprawia, że programowanie w Rubim dla wielu ludzi jest frajdą.

No tak… Ale w sumie, każda z tych metod jest w środku i tak poprzez taką pętlę for (lub inną, nie wiem bo nie patrzałem w kod źródłowy Rubiego) zaimplementowana. Zatem to tylko taka ładna obudowa. I właśnie tego chciałem się dowiedzieć :slight_smile:

Nie do końca. Tak jak wspomniałem, nie wszystkie iteratory mają dostęp poprzez indeks, np. wczytywanie linia po linii z pliku jest sekwencyjne i nie można odwołać się do konkretnej linii (chyba, że wczytamy wszystkie linie do tablicy). Zatem implementacja takiego iteratora zapewne wygląda tak:

# pseudo kod class File def each_line while line = self.readline yield line end end end