Итераторы не являются оригинальной концепцией в 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
Вы поняли, что является итератором? Имеется несколько ограничений в оригинальных итераторах, но фактически, всякий раз, когда вы определяете новый тип данных, часто бывает удобно определить подходящие итераторы для этого. В этом случае, приведённые выше примеры не очень полезны. Мы можем поговорить об этом после того, как лучше поймем, что такое классы.