Как в Elixir реализованы условные конструкции: if, case, cond, with?

В Elixir условные конструкции реализованы функционально и декларативно, без привычных операторов switch, while или традиционного if-else в императивном стиле. Вместо этого используются конструкции if, unless, case, cond и with, каждая из которых заточена под определённые задачи сопоставления с образцом и ветвления логики.

if и unless

if

Это простейшая условная конструкция. Работает так же, как в других языках, но обязательно требует возвращаемое значение, так как всё в Elixir — выражение.

if 1 + 1 == 2 do
"Истина"
else
"Ложь"
end

Можно опустить else, если логика не требует альтернативной ветки.

unless

Обратная по логике к if. Выполняет блок, если условие ложно:

unless is_nil(value) do
IO.puts("Значение не nil")
end

if и unless — это макросы, а не встроенные ключевые слова, определённые в модуле Kernel.

case

case используется для сопоставления с образцом (pattern matching) и выбора ветки в зависимости от структуры данных или значения.

case {1, 2, 3} do
{4, 5, 6} ->
"Не совпало"
{1, x, 3} ->
"Совпало! x = #{x}"
_ ->
"Ни один шаблон не подошёл"
end

Каждая ветка case — это шаблон. Можно добавлять guard-выражения (when) для фильтрации:

case value do
x when is_integer(x) and x > 0 ->
"Положительное число"
_ ->
"Не integer или не больше нуля"
end

Если ни одно условие не совпадает — будет выброшено исключение CaseClauseError.

cond

cond — альтернатива вложенным if/else if. Проверяет список логических условий и исполняет первое истинное.

cond do
2 + 2 == 5 ->
"Математика поломана"
1 + 1 == 2 ->
"Работает"
true ->
"По умолчанию"
end

В отличие от case, здесь не сопоставляются шаблоны, а просто проверяются логические выражения.

Важно: если ни одно условие не истинно и нет последнего true, будет выброшено CondClauseError.

with

with используется для цепочки сопоставлений с образцом и вызова последнего блока, если всё проходит успешно. Это упрощает цепочки case или if с множественными проверками.

with {:ok, file} <- File.read("path/to/file"),
{:ok, json} <- Jason.decode(file) do
IO.inspect(json)
else
{:error, reason} -> IO.puts("Ошибка: #{reason}")
end

В этом примере:

  • Сначала читается файл.

  • Затем происходит декодирование JSON.

  • Если на любом шаге возникает ошибка — выполняется else.

Каждое выражение после <- должно возвращать сопоставляемый кортеж или значение.

Особенности

  • Все конструкции возвращают значение (как и всё в Elixir).

  • Нет циклов в привычном понимании — всё заменяется рекурсией, Enum, Stream.

  • Нет операторов break, continue — управление потоком достигается через pattern matching, хвостовую рекурсию и функциональные абстракции.

  • Условные конструкции используются как часть выражений, а не управляющие операторы как в императивных языках.

Когда использовать что

Конструкция Когда использовать
if Простое логическое условие
--- ---
unless Противоположное if, читается как "если не..."
--- ---
case Сопоставление с шаблонами
--- ---
cond Множество логических условий (аналог if...else if...)
--- ---
with Последовательность зависимых выражений с сопоставлением
--- ---

Примеры

С if

defmodule Example do
def greet_user(name) do
if name do
"Привет, #{name}!"
else
"Привет, гость!"
end
end
end

С case и guard

defmodule Example do
def number_type(n) do
case n do
x when is_integer(x) and x > 0 -> "Положительное"
x when is_integer(x) and x &lt; 0 -&gt; "Отрицательное"
_ -> "Не integer или ноль"
end
end
end

С cond

defmodule Example do
def classify(age) do
cond do
age &lt; 13 -&gt; "Ребёнок"
age &lt; 18 -&gt; "Подросток"
age >= 18 -> "Взрослый"
end
end
end

С with

defmodule Example do
def get_user_name(id) do
with {:ok, user} <- DB.get_user(id),
true <- user.active do
{:ok, user.name}
else
_ -> {:error, "Пользователь не найден или неактивен"}
end
end
end