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