Выражения управления
В Ruby существует множество способов управления выполнением. Все описанные здесь выражения возвращают значение.
Для тестов в этих выражениях управления nil и false являются ложными значениями, а true и любой другой объект — истинными значениями. В данном документе «истина» будет означать «истинное значение», а «ложь» — «ложное значение».
if выражение
Простейшее if выражение состоит из двух частей: выражения «теста» и выражения «then». Если выражение «теста» вычисляется как истинное, то выполняется выражение «then».
Вот простой оператор 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 является последнее выполненное значение в выражении.
Тernary if
Вы также можете записать выражение if-then-else, используя ? и : . Это условное выражение:
input_type = gets =~ /hello/i ? "greeting" : "other"
То же самое, что и это if выражение:
input_type =
if gets =~ /hello/i
"greeting"
else
"other"
end
Хотя условное выражение короче, чем более развёрнутая форма, для лучшей читаемости рекомендуется использовать его только для простых условий. Также следует избегать использования нескольких условных выражений в одном выражении, так как это может быть запутанно.
unless выражение
unless выражение — это противоположность if выражению. Если значение ложно, то выполняется выражение «then»:
unless true puts "the value is a false-value" end
Это не выводит ничего, так как true не является ложным значением.
Вы можете использовать необязательное then с unless так же, как и в if.
Обратите внимание, что приведенное выше unless выражение эквивалентно:
if not true puts "the value is a false-value" end
Как и в выражении if, вы можете использовать условное выражение else с unless:
unless true puts "the value is false" else puts "the value is true" end
Это выводит «значение истинно» из условия else.
Вы не можете использовать elsif с выражением unless.
Значением результата выражения unless является последнее выполненное значение в выражении.
Модификаторы if и unless
if и unless также могут использоваться для модификации выражения. При использовании в качестве модификатора левая часть является оператором «then», а правая часть — выражением «теста»:
a = 0 a += 1 if a.zero? p a
Это выведет 1.
a = 0 a += 1 unless a.zero? p a
Это выведет 0.
Хотя у модификаторов и стандартных версий есть выражения «теста» и «then», они не являются точным преобразованием друг друга из-за порядка парсинга. Вот пример, который демонстрирует разницу:
p a if a = 0.zero?
Это вызывает исключение NameError «неопределённая локальная переменная или метод ‘a’».
При парсинге Ruby встречает a как вызов метода в выражении «then», а затем видит присвоение a в выражении «теста» и отмечает a как локальную переменную.
При выполнении этой строки сначала выполняется выражение «теста», a = 0.zero?.
Поскольку тест истинен, выполняется выражение «then», 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”».
Вы можете использовать then после условия when . Это чаще всего используется для размещения тела 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.
for цикл редко используется в современных программах на Ruby.
Модификаторы 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. Это больше не так, теперь вы получите SyntaxError, когда используете retry вне блока rescue. Смотрите Исключения для правильного использования 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. Переключатель изначально выключен (false) для 0 и 1, но включается (true) для 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, если существует соответствующий метод throw внутри блока catch. Если внутри блока 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–2024 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.