list = %w[alpha bravo charlie delta echo];
В цикле этот массив обходится и печатается каждый его элемент.
Таблица 1.2. Циклы
# Цикл 1 (while) | # Цикл 2 (until) |
---|---|
i=0 while i < list.size do print "#{list[i]} " i += 1 end |
i=0 until i == list.size do print "#{list[i]} " i += 1 end |
# Цикл 3 (for) | # Цикл 4 (итератор 'each') |
for x in list do print "#{x} " end |
list.each do |x| print "#{x} " end |
# Цикл 5 (метод 'loop') | # Цикл 6 (метод 'loop') |
i = 0 n=list.size-1 loop do print "#{list[i]} " i += 1 break if i > n end |
i=0 n=list.size-1 loop do print "#{list[i]} " i += 1 break unless i <= n end |
# Цикл 7 (итератор 'times') | # Цикл 8 (итератор 'upto') |
n=list.size n.times do |i| print "#{list[i]} " end |
n=list.size-1 0.upto(n) do |i| print "#{list[i]} " end |
# Цикл 9 (for) | # Цикл 10 ('each_index') |
n=list.size-1 for i in 0..n do print "#{list[i]} " end |
list.each_index do |x| print "#{list[x]} " end |
Рассмотрим эти примеры более подробно. Циклы 1 и 2 — «стандартные» формы циклов while
и until
; ведут они себя практически одинаково, только условия противоположны. Циклы 3 и 4 — варианты предыдущих с проверкой условия в конце, а не в начале итерации. Отметим, что использование слов begin
и end
в этом контексте — просто грязный трюк; на самом деле это был бы блок begin/end (применяемый для обработки исключений), за которым следует модификатор while
или until
. Однако для тех, кто желает написать цикл с проверкой в конце, разницы нет.
На мой взгляд, конструкции 3 и 4 — самый «правильный» способ кодирования циклов. Они заметно проще всех остальных: нет ни явной инициализации, ни явной проверки или инкремента. Это возможно потому, что массив «знает» свой размер, а стандартный итератор each
(цикл 6) обрабатывает такие детали автоматически. На самом деле в цикле 5 производится неявное обращение к этому итератору, поскольку цикл for
работает с любым объектом, для которого определен итератор each
. Цикл for
— лишь сокращенная запись для вызова each
; часто такие сокращения называют «синтаксической глазурью», имея в виду, что это не более чем удобная альтернативная форма другой синтаксической конструкции.
В циклах 5 и 6 используется конструкция loop
. Выше мы уже отмечали, что хотя loop
выглядит как ключевое слово, на самом деле это метод модуля Kernel
, а вовсе не управляющая конструкция.
В циклах 7 и 8 используется тот факт, что у массива есть числовой индекс. Итератор times
исполняется заданное число раз, а итератор upto
увеличивает свой параметр до заданного значения. И тот, и другой для данной ситуации приспособлены плохо.
Цикл 9 — это вариант цикла for
, предназначенный специально для работы со значениями индекса при помощи указания диапазона. В цикле 10 мы пробегаем весь диапазон индексов массива с помощью итератора each_index
.
В предыдущих примерах мы уделили недостаточно внимания вариантам циклов while
и loop
с модификаторами. Они довольно часто используются из-за краткости. Вот еще два примера, в которых делается одно и то же:
perform_task() until finished
perform_task() while not finished
Также из таблицы 1.2 осталось неясным, что циклы не всегда выполняются от начала до конца. Число итераций не всегда предсказуемо. Нужны дополнительные средства управления циклами.
Первое из них — ключевое слово break
, встречающееся в циклах 5 и 6. Оно позволяет досрочно выйти из цикла; в случае вложенных циклов происходит выход из самого внутреннего. Для программистов на С это интуитивно очевидно.
Ключевое слово retry
применяется в двух случаях: в контексте итератора и в контексте блока begin-end
(обработка исключений). В теле итератора (или цикла for
) оно заставляет итератор заново выполнить инициализацию, то есть повторно вычислить переданные ему аргументы. Отметим, что к циклам общего вида это не относится.
Ключевое слово redo
— обобщение retry
на циклы общего вида. Оно работает в циклах while
и until
, как retry
в итераторах.