Spec-Zone.ru › Ruby 3.2

Управляющие выражения

В 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, используя ? и :. Этот тернарный if:

input_type = gets =~ /hello/i ? "greeting" : "other"

Эквивалентен этому if выражению:

input_type =
  if gets =~ /hello/i
    "greeting"
  else
    "other"
  end

Хотя тернарный if гораздо короче, чем более подробная форма, для лучшей читабельности рекомендуется использовать тернарный if только для простых условных операторов. Также следует избегать использования нескольких тернарных условий в одном выражении, так как это может быть запутанно.

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. Это больше не так; теперь при использовании 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 имеет более низкий приоритет, чем =.

Flip-Flop

Flip-flop — это слегка специфическое условное выражение. Одно из его типичных применений — обработка текста из однострочных программ Ruby, используемых с ruby -n или ruby -p.

Форма flip-flop — это выражение, указывающее, когда flip-flop включается, .. (или ...), затем выражение, указывающее, когда flip-flop выключится. Пока flip-flop включен, он продолжает оцениваться как 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. Flip-flop изначально «выключен» (false) для 0 и 1, но становится «включен» (true) для 2 и остаётся «включенным» до 8. После 8 он выключается и остаётся «выключенным» для 9 и 10.

Flip-flop должен использоваться внутри условного оператора, такого как !, ? :, not, if, while, unless, until и т. п., включая модифицированные формы.

При использовании включительного диапазона (..) условие выключения оценивается при изменении условия включения:

selected = []

0.upto 5 do |value|
  selected << value if value==2..value==2
end

p selected # prints [2]

Здесь обе стороны flip-flop оцениваются, поэтому flip-flop включается и выключается только тогда, когда value равно 2. Так как flip-flop включился в итерации, он возвращает true.

При использовании исключительного диапазона (...) условие выключения оценивается на следующей итерации:

selected = []

0.upto 5 do |value|
  selected << value if value==2...value==2
end

p selected # prints [2, 3, 4, 5]

Здесь flip-flop включается, когда value равно 2, но не выключается в той же итерации. Условие выключения оценивается только на следующей итерации, и value больше никогда не будет равно двум.

Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API