Итераторы не являются чем-то, свойственным только Ruby. Они общеупотребительны в объектно-ориентированных языках программирования. Они также есть в Lisp, хотя там они не называются итераторами. Как бы то ни было, концепция итераторов не является близкой множеству людей, так что давайте рассмотрим ее более детально.
Глагол iterate (повторять) означает "проделывать одно и то же много (ну, несколько) раз, так что итератор (iterator) - это нечто, делающее одно и то же много раз.
Когда мы программируем, то нуждаемся в использовании циклов во множестве различных ситуаций. В С мы кодируем их, используя for или while. Например,
char *str;
for (str = "abcdefg"; *str != ′\0′; str++) {
/* process a character here */
}
Синтакс оператора C for(...) обеспечивает абстракцию, помогающую в создании циклов, но для проверки *str на null необходимо знание программистом деталей о внутреннем представлении строки. Это дает представление о С как о низкоуровневом языке программирования. Высокоуровневые языки отличает более гибкая поддержка ими итераторов. Посмотрите на скрипт sh :
#!/bin/sh
for i in *.[ch]; do
# ... этот код будет выполнен для каждой строки
done
Обрабатываются все исходники на С и хидеры в текущем каталоге , а shell заботится о деталях получения и подстановки имен файлов один за одним. Думаю, это работает на несколько более высоком уровне, чем С, а?
Можно покопать глубже: хотя и хорошо, что в языке есть поддержка для создания итераторов по встроенным типам данных, но разочаровывает, что когда необходима итерация по собственным типам данных, нужно возвращаться к низкоуровневым циклам. В ООП Пользователи часто создают один за другим собственные типы данных, так что такая ситуация может создать серьезные проблемы.
Так, каждый язык ООП включает в себя некоторые средства поддержки итераторов. В некоторых языках для этой цели существуют специальные классы; Ruby же позволяет определять итераторы напрямую.
В Ruby для типа String имеется несколько полезных итераторов:
ruby> "abc".each_byte{|c| printf "<%c>", c}; print "\n"
<a><b><c>
nil
each_byte - итератор для каждого символа в строке. Каждый символ подставляется в локальную переменную c. Это может быть представлено как нечто, весьма напоминающее код на С...
ruby> s="abc";i=0
0
ruby> while i<s.length
| printf "<%c>", s; 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 для String делает то же самое, что и 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
Понятно, что такое итератор? Есть некоторые ограничения, но Вы можете написать собственные итераторы; фактически, когда Вы создаете новый тип данных, то часто удобно определить подходящий для него итератор. В этом смысле приведенные выше примере не слишком полезны. Мы сможем говорить о практическом применении итераторов после того, как будем иметь лучшее представление о том, что же такое классы.
К началу статьи