Управление потоком выполнения
Примитивные типы
Nil
Самый простой тип — Nil. Он имеет только одно значение: nil и обозначает отсутствие действительного значения.
Помните String#index из прошлого урока? Оно возвращает nil, если подстрока не найдена в строке поиска. У неё нет индекса, поэтому позиция индекса отсутствует.
p! "Crystal is awesome".index("aw"),
"Crystal is awesome".index("xxxx")
Bool
Тип Bool имеет всего два возможных значения: true и false, которые представляют значения истинности в логике и булевой алгебре.
p! true, false
Булевы значения особенно полезны для управления потоком выполнения программы.
Булева алгебра
В следующем примере показаны операторы для реализации булевой алгебры с булевыми значениями:
a = true
b = false
p! a && b, # conjunction (AND)
a || b, # disjunction (OR)
!a, # negation (NOT)
a != b, # inequivalence (XOR)
a == b # equivalence
Попробуйте переключать значения a и b, чтобы увидеть поведение операторов при разных входных значениях.
Истинность
Однако булева алгебра применима не только к булевым типам. Все значения обладают неявной истинностью: nil, false и нулевые указатели (для полноты картины мы рассмотрим их позже) являются ложными. Любое другое значение (включая 0) является истинным.
Давайте заменим true и false в примере выше другими значениями, например "foo" и nil.
a = "foo"
b = nil
p! a && b, # conjunction (AND)
a || b, # disjunction (OR)
!a, # negation (NOT)
a != b, # inequivalence (XOR)
a == b # equivalence
Операторы AND и OR возвращают значение первого операнда, соответствующее истинности оператора.
p! "foo" && nil,
"foo" && false,
false || "foo",
"bar" || "foo"
Операторы NOT, XOR и эквивалентности всегда возвращают значение типа Bool (true или false).
Управление потоком выполнения
Управлять потоком выполнения программы — значит выбирать разные пути в зависимости от условий. До этого момента все программы в руководстве представляли собой последовательность выражений. Теперь это изменится.
Условные конструкции
Условная конструкция помещает ветвь кода за воротами, которые открываются только при выполнении условия.
В простейшем виде она состоит из ключевого слова if, за которым следует выражение, служащее условием. Условие считается выполненным, если возвращаемое выражением значение является истинным. Все последующие выражения относятся к этой ветви, пока она не завершится ключевым словом end.
По соглашению вложенные ветви выделяются отступом в два пробела.
Следующий пример выводит сообщение, только если оно начинается с Hello.
message = "Hello World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
end
Примечание
Технически эта программа по-прежнему выполняется в заранее заданном порядке. Фиксированное сообщение всегда соответствует условию и делает его истинным. Но предположим, что мы не задаём значение сообщения в исходном коде. Оно вполне может поступить от пользователя, например из клиента чата.
Если значение сообщения не начинается с Hello, условная ветвь пропускается, и программа ничего не выводит.
Условное выражение может быть сложнее. С помощью булевой алгебры можно составить условие, принимающее либо Hello, либо Hi:
message = "Hello World"
if message.starts_with?("Hello") || message.starts_with?("Hi")
puts "Hey there!"
end
Давайте развернём условие: выводить сообщение, только если оно не начинается с Hello. Это лишь небольшое изменение предыдущего примера: можно использовать оператор отрицания (!), чтобы заменить условие на противоположное выражение.
message = "Hello World"
if !message.starts_with?("Hello")
puts "I didn't understand that."
end
Другой вариант — заменить if ключевым словом unless, которое ожидает противоположное значение истинности. unless x эквивалентно if !x.
message = "Hello World"
unless message.starts_with?("Hello")
puts "I didn't understand that."
end
Рассмотрим пример, в котором используется String#index для поиска подстроки и выделения её расположения. Помните, что метод возвращает nil, если не может найти подстроку? В таком случае выделить ничего не получится. Поэтому нам нужна конструкция if с условием, проверяющим, является ли индекс значением nil. Для этого отлично подходит метод .nil?.
str = "Crystal is awesome"
index = str.index("aw")
if !index.nil?
puts str
puts "#{" " * index}^^"
end
Компилятор требует обработать случай nil. Попробуйте удалить условную конструкцию или изменить условие на true: появится ошибка типа, объясняющая, что в этом выражении нельзя использовать значение Nil. При правильном условии компилятор знает, что внутри ветви index не может быть nil, поэтому его можно использовать как числовой аргумент.
Совет
Более краткая форма для if !index.nil? — if index, которая в большинстве случаев эквивалентна. Разница возникает только в том случае, если нужно различать, является ли ложное значение nil или false, поскольку первое условие срабатывает для false, а второе — нет.
Else
Давайте усовершенствуем программу и предусмотрим реакцию для обоих случаев — когда сообщение соответствует условию и когда нет.
Это можно сделать с помощью двух отдельных условных конструкций с отрицательными условиями:
message = "Hello World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
end
if !message.starts_with?("Hello")
puts "I didn't understand that."
end
Это работает, но есть два недостатка: условное выражение message.starts_with?("Hello") вычисляется дважды, что неэффективно. Позже, если мы изменим условие в одном месте (например, разрешив также Hi), то можем забыть изменить его и в другом месте.
Условная конструкция может иметь несколько ветвей. Альтернативная ветвь обозначается ключевым словом else. Она выполняется, если условие не выполнено.
message = "Hello World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
else
puts "I didn't understand that."
end
Дополнительные ветви
Наша программа реагирует только на Hello, но мы хотим добавить взаимодействия. Добавим ветвь, которая будет отвечать и на Bye. В одной условной конструкции можно предусмотреть ветви для разных условий. Это похоже на else с ещё одним встроенным if. Поэтому используется ключевое слово elsif:
message = "Bye World"
if message.starts_with?("Hello")
puts "Hello to you, too!"
elsif message.starts_with?("Bye")
puts "See you later!"
else
puts "I didn't understand that."
end
Ветвь else по-прежнему выполняется, только если ни одно из предыдущих условий не выполнено. Впрочем, её всегда можно опустить.
Обратите внимание: разные ветви взаимоисключающие, а условия проверяются сверху вниз. В примере выше это не имеет значения, поскольку оба условия не могут быть истинными одновременно (сообщение не может начинаться и с Hello, и с Bye). Однако для наглядности добавим альтернативное условие, которое не является взаимоисключающим:
message = "Hello Crystal"
if message.starts_with?("Hello")
puts "Hello to you, too!"
elsif message.includes?("Crystal")
puts "Shine bright like a crystal."
end
if message.includes?("Crystal")
puts "Shine bright like a crystal."
elsif message.starts_with?("Hello")
puts "Hello to you, too!"
end
В обеих конструкциях есть ветви с одинаковыми условиями, но в разном порядке, и ведут они себя по-разному. Какая ветвь выполнится, определяется первым совпавшим условием.
To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystal-lang.org/reference/1.2/tutorials/basics/50_control_flow.html