perform_task() until finished
perform_task() while not finished
Также из таблицы 1.2 осталось неясным, что циклы не всегда выполняются от начала до конца. Число итераций не всегда предсказуемо. Нужны дополнительные средства управления циклами.
Первое из них — ключевое слово break
, встречающееся в циклах 5 и 6. Оно позволяет досрочно выйти из цикла; в случае вложенных циклов происходит выход из самого внутреннего. Для программистов на С это интуитивно очевидно.
Ключевое слово retry
применяется в двух случаях: в контексте итератора и в контексте блока begin-end
(обработка исключений). В теле итератора (или цикла for
) оно заставляет итератор заново выполнить инициализацию, то есть повторно вычислить переданные ему аргументы. Отметим, что к циклам общего вида это не относится.
Ключевое слово redo
— обобщение retry
на циклы общего вида. Оно работает в циклах while
и until
, как retry
в итераторах.
Ключевое слово next
осуществляет переход на конец самого внутреннего цикла и возобновляет исполнение с этой точки. Работает для любого цикла и итератора.
Как мы только что видели, итератор — важное понятие в Ruby. Но следует отметить, что язык позволяет определять и пользовательские итераторы, не ограничиваясь встроенными.
Стандартный итератор для любого объекта называется each
. Это существенно отчасти из-за того, что позволяет использовать цикл for
. Но итераторам можно давать и другие имена и применять для разных целей.
В качестве примера рассмотрим многоцелевой итератор, который имитирует цикл с проверкой условия в конце (как в конструкции do-while
в С или repeat-until
в Pascal):
def repeat(condition)
yield
retry if not condition
end
В этом примере ключевое слово yield
служит для вызова блока, который задается при таком вызове итератора:
j=0
repeat (j >= 10) do
j += 1
puts j
end
С помощью yield
можно также передать параметры, которые будут подставлены в список параметров блока (между вертикальными черточками). В следующем искусственном примере итератор всего лишь генерирует целые числа от 1 до 10, а вызов итератора порождает кубические степени этих чисел:
def my_sequence
for i in 1..10 do
yield i
end
end
my_sequence {|x| puts x**3 }
Отметим, что вместо фигурных скобок, в которые заключен блок, можно написать ключевые слова do
и end
. Различия между этими формами есть, но довольно тонкие.
1.2.7. Исключения
Как и многие другие современные языки, Ruby поддерживает исключения.
Предложение raise
возбуждает исключение. Отметим, что raise
— не зарезервированное слово, а метод модуля Kernel
. (У него есть синоним fail
.)
raise # Пример 1
raise 'Произошла ошибка' # Пример 2
raise ArgumentError # Пример 3
raise ArgumentError, 'Неверные данные' # Пример 4
raise ArgumentError.new('Неверные данные ') # Пример 5
raise ArgumentError, ' Неверные данные ', caller[0] # Пример 6
В первом примере повторно возбуждается последнее встретившееся исключение. В примере 2 создается исключение RuntimeError
(подразумеваемый тип), которому передается сообщение 'Произошла ошибка'
.
В примере 3 возбуждается исключение типа ArgumentError
, а в примере 4 такое же исключение, но с сообщением 'Неверные данные'
. Пример 5 — просто другая запись примера 4. Наконец, в примере 6 еще добавляется трассировочная информация вида 'filename:line'
или 'filename:line:in 'method''
(хранящаяся в массиве caller
).
А как обрабатываются исключения в Ruby? Для этой цели служит блок begin-end
. В простейшей форме внутри него нет ничего, кроме кода:
begin
#Ничего полезного.
#...
end
Просто перехватывать ошибки не очень осмысленно. Но у блока может быть один или несколько обработчиков rescue
. Если произойдет ошибка в любой точке программы между begin
и rescue
, то управление сразу будет передано в подходящий обработчик rescue
.
begin
x = Math.sqrt(y/z)
# ...
rescue ArgumentError
puts 'Ошибка при извлечении квадратного корня.'
rescue ZeroDivisionError
puts 'Попытка деления на нуль.'
end
Того же эффекта можно достичь следующим образом:
begin
x = Math.sqrt(y/z)
# ...
rescue => err
puts err