Управляющие выражения
В Ruby есть множество способов управлять выполнением программы. Все описанные здесь выражения возвращают значение.
В проверках этих управляющих выражений nil и false являются ложными значениями, а true и любые другие объекты — истинными значениями. В этом документе «истина» означает «истинное значение», а «ложь» — «ложное значение».
Выражение if
Простейшее выражение if состоит из двух частей: выражения-проверки и выражения «тогда». Если выражение-проверка вычисляется как истинное, вычисляется выражение «тогда».
Вот простой оператор if:
if true then puts "the test resulted in a true-value" end
Он выведет «проверка дала истинное значение».
then необязателен:
if true puts "the test resulted in a true-value" end
В этом документе необязательный then будет опущен во всех выражениях, поскольку это наиболее распространённый способ использования if.
Также можно добавить выражение else. Если проверка не вычисляется как истинная, будет выполнено выражение else:
if false puts "the test resulted in a true-value" else puts "the test resulted in a false-value" end
Он выведет «проверка дала ложное значение».
В выражение if можно добавить любое количество дополнительных проверок с помощью elsif. elsif выполняется, если все проверки перед elsif ложны.
a = 1 if a == 0 puts "a is zero" elsif a == 1 puts "a is one" else puts "a is some other value" end
Будет выведено «a равно единице», поскольку 1 не равно 0. Поскольку else выполняется только в том случае, если ни одно условие не подошло.
Как только условие совпадает — будь то условие if или любое условие elsif, — выражение if завершается, и дальнейшие проверки не выполняются.
Как и за условием if, за условием elsif может следовать then.
В этом примере выводится только «a равно единице»:
a = 1 if a == 0 puts "a is zero" elsif a == 1 puts "a is one" elsif a >= 1 puts "a is greater than or equal to one" else puts "a is some other value" end
Проверки для if и elsif могут иметь побочные эффекты. Чаще всего побочный эффект используется для сохранения значения в локальную переменную:
if a = object.some_value # do something to a end
Результатом выражения if является последнее вычисленное в нём значение.
Тернарный if
Также можно записать выражение if-then-else с помощью ? и :. Этот тернарный if:
input_type = gets =~ /hello/i ? "greeting" : "other"
Эквивалентен этому выражению if:
input_type =
if gets =~ /hello/i
"greeting"
else
"other"
end
Тернарный if записать намного короче, чем более многословный вариант, однако для удобства чтения рекомендуется использовать его только для простых условий. Также избегайте нескольких тернарных условий в одном выражении, поскольку это может затруднить его понимание.
Выражение unless
Выражение unless противоположно выражению if. Если значение ложно, выполняется выражение «тогда»:
unless true puts "the value is a false-value" end
Ничего не выводится, поскольку true не является ложным значением.
Как и с if, с unless можно использовать необязательный then.
Обратите внимание: приведённое выше выражение unless эквивалентно следующему:
if not true puts "the value is a false-value" end
Как и в выражении if, с unless можно использовать условие else:
unless true puts "the value is false" else puts "the value is true" end
Из условия else выводится «значение истинно».
Нельзя использовать elsif с выражением unless.
Результатом выражения unless является последнее вычисленное в нём значение.
Модификаторы if и unless
if и unless также можно использовать как модификаторы выражения. При использовании в качестве модификатора левая часть является оператором «тогда», а правая — выражением-проверкой:
a = 0 a += 1 if a.zero? p a
Будет выведено 1.
a = 0 a += 1 unless a.zero? p a
Будет выведено 0.
Хотя в вариантах с модификатором и в стандартных вариантах есть и выражение-проверка, и оператор «тогда», они не являются точными преобразованиями друг друга из-за порядка разбора. Вот пример, показывающий разницу:
p a if a = 0.zero?
Это вызывает NameError «undefined local variable or method ‘a’».
При разборе этого выражения Ruby сначала встречает a как вызов метода в выражении «тогда», а позднее видит присваивание a в выражении-проверке и помечает a как локальную переменную.
При выполнении этой строки сначала выполняется выражение-проверка — a = 0.zero?.
Поскольку проверка истинна, выполняется выражение «тогда» — p a. Так как a в теле был распознан как вызов несуществующего метода, возникает NameError.
То же верно для unless.
Выражение case
Выражение case можно использовать двумя способами.
Наиболее распространённый способ — сравнить объект с несколькими шаблонами. Сопоставление с шаблонами выполняется с помощью метода ===, который является псевдонимом == в Object. Остальные классы должны переопределить этот метод, чтобы обеспечить осмысленное поведение. Примеры см. в описаниях Module#=== и Regexp#===.
Вот пример использования case для сравнения String с шаблоном:
case "12345" when /^1/ puts "the string starts with one" else puts "I don't know what the string starts with" end
Здесь строка "12345" сравнивается с /^1/ вызовом /^1/ === "12345", который возвращает true. Как и в выражении if, выполняется первое совпавшее when, а все остальные совпадения игнорируются.
Если совпадений нет, выполняется else.
else и then необязательны; это выражение case даёт тот же результат, что и приведённое выше:
case "12345" when /^1/ puts "the string starts with one" end
Для одного и того же when можно указать несколько условий:
case "2" when /^1/, "2" puts "the string starts with one or is '2'" end
Ruby проверит условия по очереди: сначала /^1/ === "2" возвращает false, затем "2" === "2" возвращает true, поэтому выводится «строка начинается с единицы или равна ‘2’».
После условия when можно использовать then. Чаще всего это делают, чтобы разместить тело when в одной строке.
case a when 1, 2 then puts "a is one or two" when 3 then puts "a is three" else puts "I don't know what a is" end
Другой способ использовать выражение case — подобно выражению if-elsif:
a = 2 case when a == 1, a == 2 puts "a is one or two" when a == 3 puts "a is three" else puts "I don't know what a is" end
И снова, then и else необязательны.
Результатом выражения case является последнее вычисленное в нём значение.
Начиная с Ruby 2.7, выражения case также предоставляют более мощную возможность сопоставления с шаблонами с помощью ключевого слова in:
case {a: 1, b: 2, c: 3}
in a: Integer => m
"matched: #{m}"
else
"not matched"
end
# => "matched: 1"
Синтаксис сопоставления с шаблонами описан на отдельной странице.
Цикл while
Цикл while выполняется, пока условие истинно:
a = 0 while a < 10 do p a a += 1 end p a
Выводит числа от 0 до 10. Условие a < 10 проверяется перед входом в цикл, затем выполняется его тело, после чего условие проверяется снова. Когда результат проверки становится ложным, цикл завершается.
Ключевое слово do необязательно. Следующий цикл эквивалентен приведённому выше:
while a < 10 p a a += 1 end
Результат цикла while — nil, если значение не задано с помощью break.
Цикл until
Цикл until выполняется, пока условие ложно:
a = 0 until a > 10 do p a a += 1 end p a
Выводит числа от 0 до 11. Как и в цикле while, условие a > 10 проверяется при входе в цикл и после каждого выполнения его тела. Если условие ложно, цикл продолжает выполняться.
Как и в цикле while, do необязательно.
Как и в цикле while, результат цикла until равен nil, если не используется break.
Цикл for
Цикл for состоит из for, за которым следует переменная для сохранения аргумента итерации, затем in и значение, по которому выполняется итерация с помощью each. Элемент do необязателен:
for value in [1, 2, 3] do puts value end
Выводит 1, 2 и 3.
Как и в while и until, do необязательно.
Цикл for похож на использование each, но не создаёт новую область видимости переменных.
Результатом цикла for является перебираемое значение, если не используется break.
В современных программах на Ruby цикл for используется редко.
Модификаторы while и until
Как и if и unless, while и until можно использовать в качестве модификаторов:
a = 0 a += 1 while a < 10 p a # prints 10
until в качестве модификатора:
a = 0 a += 1 until a > 10 p a # prints 11
С помощью begin и end можно создать цикл while, который выполнит тело один раз перед проверкой условия:
a = 0 begin a += 1 end while a < 10 p a # prints 10
Если не использовать rescue или ensure, Ruby оптимизирует накладные расходы на обработку исключений.
Оператор break
Используйте break, чтобы досрочно выйти из блока. Это прекратит перебор элементов в values, если один из них окажется чётным:
values.each do |value| break if value.even? # ... end
Также можно завершить цикл while с помощью break:
a = 0 while true do p a a += 1 break if a < 10 end p a
Выводит числа 0 и 1.
break принимает значение, которое становится результатом выражения, из которого выполняется выход:
result = [1, 2, 3].each do |value| break value * 2 if value.even? end p result # prints 4
Оператор next
Используйте next, чтобы пропустить остаток текущей итерации:
result = [1, 2, 3].map do |value| next if value.even? value * 2 end p result # prints [2, nil, 6]
next принимает аргумент, который можно использовать как результат текущей итерации блока:
result = [1, 2, 3].map do |value| next value if value.even? value * 2 end p result # prints [2, 2, 6]
Оператор redo
Используйте redo, чтобы повторить текущую итерацию:
result = [] while result.length < 10 do result << result.length redo if result.last.even? result << result.length + 1 end p result
Выводит [0, 1, 3, 3, 5, 5, 7, 7, 9, 9, 11]
В Ruby 1.8 можно было использовать retry там, где использовался redo. Теперь это не так: при использовании retry вне блока rescue возникает SyntaxError. Правильное использование retry описано в разделе «Исключения».
Операторы-модификаторы
Грамматика Ruby различает операторы и выражения. Все выражения являются операторами (выражение — это разновидность оператора), но не все операторы являются выражениями. Некоторые части грамматики допускают выражения, но не другие типы операторов, из-за чего похожий код может разбираться по-разному.
Например, если if, else, while, until и begin не используются как модификаторы, они являются выражениями (а также операторами). Однако при использовании в качестве модификаторов if, else, while, until и rescue являются операторами, но не выражениями.
if true; 1 end # expression (and therefore statement) 1 if true # statement (not expression)
Операторы, не являющиеся выражениями, нельзя использовать в контекстах, где ожидается выражение, например в аргументах метода.
puts( 1 if true ) #=> SyntaxError
Чтобы создать выражение, можно заключить оператор в круглые скобки.
puts((1 if true)) #=> 1
Если поставить пробел между именем метода и открывающей скобкой, второй набор скобок не понадобится.
puts (1 if true) #=> 1, because of optional parentheses for method
Это происходит потому, что выражение разбирается как вызов метода без скобок. Оно эквивалентно следующему коду, но без создания локальной переменной:
x = (1 if true) p x
В операторе-модификаторе левая часть должна быть оператором, а правая — выражением.
Поэтому в a if b rescue c, поскольку b rescue c является оператором, но не выражением и потому не может находиться справа от оператора-модификатора if, код неизбежно разбирается как (a if b) rescue c.
Из-за взаимодействия с приоритетом операторов:
stmt if v = expr rescue x stmt if v = expr unless x
разбираются следующим образом:
stmt if v = (expr rescue x) (stmt if v = expr) unless x
Это происходит потому, что модификатор rescue имеет более высокий приоритет, чем =, а модификатор if имеет более низкий приоритет, чем =.
Триггер
Триггер — это несколько особое условное выражение. Обычно его используют для обработки текста в однострочных программах Ruby, запускаемых с помощью ruby -n или ruby -p.
Форма триггера — это выражение, указывающее, когда он включается, .. (или ...), за которым следует выражение, указывающее, когда он выключается. Пока триггер включён, он продолжает вычисляться как true, а в выключенном состоянии — как false.
Вот пример:
selected = [] 0.upto 10 do |value| selected << value if value==2..value==8 end p selected # prints [2, 3, 4, 5, 6, 7, 8]
В приведённом выше примере условие включения — n==2. Изначально триггер выключен (ложен) для значений 0 и 1, но включается (становится истинным) при значении 2 и остаётся включённым до 8 включительно. После 8 он выключается и остаётся выключенным для 9 и 10.
Триггер необходимо использовать внутри условного выражения, например !, ? :, not, if, while, unless, until и т. д., в том числе в вариантах с модификаторами.
При использовании включительного диапазона (..) условие выключения проверяется в момент изменения условия включения:
selected = [] 0.upto 5 do |value| selected << value if value==2..value==2 end p selected # prints [2]
Здесь проверяются обе стороны триггера, поэтому он включается и выключается только тогда, когда value равно 2. Поскольку триггер включился в этой итерации, он возвращает true.
При использовании исключающего диапазона (...) условие выключения проверяется на следующей итерации:
selected = [] 0.upto 5 do |value| selected << value if value==2...value==2 end p selected # prints [2, 3, 4, 5]
Здесь триггер включается, когда value равно 2, но не выключается в той же итерации. Условие выключения проверяется только на следующей итерации, а value больше никогда не будет равно двум.
throw/catch
throw и catch используются в Ruby для реализации нелокального управления потоком. Они работают подобно исключениям, позволяя передать управление напрямую из места вызова throw в место вызова соответствующего catch. Главное отличие throw/catch от исключений заключается в том, что throw/catch предназначены для ожидаемых изменений потока управления, а исключения — для непредвиденных ситуаций, например обработки неожиданных ошибок.
При использовании throw передаются 1–2 аргумента. Первый аргумент — значение для соответствующего catch. Второй аргумент необязателен (по умолчанию равен nil) и будет значением, которое возвращает catch, если внутри блока catch будет вызван соответствующий throw. Если внутри блока catch не вызывается соответствующий метод throw, метод catch возвращает значение, возвращённое переданным ему блоком.
def a(n)
throw :d, :a if n == 0
b(n)
end
def b(n)
throw :d, :b if n == 1
c(n)
end
def c(n)
throw :d if n == 2
end
4.times.map do |i|
catch(:d) do
a(i)
:default
end
end
# => [:a, :b, nil, :default]
Если первый аргумент, переданный в throw, не обработан соответствующим catch, возникает исключение UncaughtThrowError. Это связано с тем, что throw/catch следует использовать только для ожидаемых изменений потока управления, поэтому использование значения, которое заранее не ожидалось, является ошибкой.
throw/catch реализованы как методы Kernel (Kernel#throw и Kernel#catch), а не как ключевые слова. Поэтому их нельзя использовать напрямую в контексте BasicObject. В этом случае можно использовать Kernel.throw и Kernel.catch:
BasicObject.new.instance_exec do
def a
b
end
def b
c
end
def c
::Kernel.throw :d, :e
end
result = ::Kernel.catch(:d) do
a
end
result # => :e
end
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.