Ітератори не є оригінальною концепцією у Ruby. Вони є типовим явищем в объектно-ориентированных мовами. Також використовуються в Lisp, хоча вони не називаються ітераторами. Однак концепція ітераторів є незнайомою для багатьох, тому має бути більш детально пояснена.
Дієслово iterate означає робити будь-яку річ багато разів, і ви знаєте, що ітератор є чимось, що виконує деяку річ багато разів.
Коли ми пишемо код, ми маємо працювати з циклами у багатьох ситуаціях. У C ми використовуватимемо for або while. Наприклад,
char *str; for (str = "abcdefg"; *str != '\0'; str++) { /* здесь идёт обработка символа */ }
C синтаксис for(...) надає абстракцію за допомогою із створенням циклу, але перевірка str на null символ вимагає від програміста знати деталі про внутрішній структурі рядка. Це створює відчуття того, що C є низькорівневою мовою. Мови високого рівня відзначені більш гнучкою підтримкою рядків. Розглянемо наступний shell скрипт sh*:
#!/bin/sh for i in *.[ch]; do # ... here would be something to do for each file done
Усі вихідні та заголовні файли C обробляються в поточній директорії, і команда shell обробляє деталі файлів підбираючи та змінюючи імена файлів один за одним. Цей рівень є більш високим, ніж у C, чи не так?
Але є щось більше для розгляду: в той час як це чудово підходить для мови, який забезпечує ітератори для вбудованих типів даних, ми маємо розчарування в тому випадку, якщо ми повинні повернутися до написання низькорівневих циклів для наших типів даних. В ООП користувачі часто визначають інші типи даних, із чим можуть бути пов'язані серйозні проблеми.
Отже, об'єктно-орієнтована мова включає деякі можливості для ітерації. Деякі мови надають спеціальні класи для цього. А Ruby дозволяє прямо визначати ітератори.
Рядок Ruby має деякі корисні ітератори:
ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n" <a><b><c> nil
each_byte є ітератором кожного символу в рядку. Кожен символ міститься в локальній змінній c . Це може бути трансльовано в щось схоже на C код.
ruby> s="abc";i=0 0 ruby> while i<s.length | printf "<%c>", s[i]; i+=1 | end; print "\n" <a><b><c> nil
... однак, ітератор each_byte є як концептуально простим, так і більш кращим для продовження роботи, навіть якщо клас String буде радикально модифікований у майбутньому. Однією з переваг ітераторів є те, що вони надійніші в умовах таких змін. Що насправді є гарною характеристикою коду. (Так, наберіться терпіння, ми поговоримо ще про те, що таке класи)
Іншим ітератором String є each_line.
ruby> "a\nb\nc\n".each_line{|l| print l} a b c nil
Завдання, для яких потрібні зусилля у програмуванні на С (пошук роздільників рядків, створення підрядків тощо) простіше обробляються з використанням ітераторів.
Оператор for, що з'явився у попередньому розділі виконує ітерацію подібно до ітератора each . Строковий each працює подібно each_line , перепишемо нижче приклад із застосуванням for :
ruby> for l in "a\nb\nc\n" | print l | end a b c nil
Ми можемо використовувати керуючу структуру retry у поєднанні з ітераціями циклу, і це повертатиме поточну ітерацію до верху ітерацій циклу.
ruby> c=0 0 ruby> for i in 0..4 | print i | if i == 2 and c == 0 | c = 1 | print "\n" | retry | end | end; print "\n" 012 01234 nil
Іноді зустрічається оператор yield у визначенні ітератора. yield переміщує управління в блок коду, який був переданий ітератору (це буде докладно розглянуто у розділі про обробку об'єктів). Наступний приклад показує ітератор repeat , який повторює блок коду стільки разів, скільки вказано в аргументі.
ruby> def repeat(num) | while num > 0 | yield | num -= 1 | end | end nil ruby> repeat(3) { print "foo\n" } foo foo foo nil
З retry , ми можемо визначити ітератор, який працює як цикл while , хоча це і занадто повільно, щоб бути практичним.
ruby> def WHILE(cond) | return if not cond | yield | retry | end nil ruby> i=0; WHILE(i<3) { print i; i+=1 } 012 nil
Ви зрозуміли, що це ітератор? Є кілька обмежень в оригінальних ітераторах, але фактично, щоразу, коли ви визначаєте новий тип даних, часто буває зручно визначити відповідні ітератори для цього. У цьому випадку наведені вище приклади не дуже корисні. Ми можемо про це поговорити після того, як краще зрозуміємо, що таке класи.