Elixir пропонує різноманітні керуючі конструкції для управління потоком виконання програми. На відміну від імперативних мов, всі конструкції в Elixir повертають значення, що робить код більш виразним.
if та else
Конструкція if перевіряє умову і виконує код відповідно до результату:
# Базовий синтаксис
if true do
"Це буде виконано"
else
"Це не буде виконано"
end
# Однорядковий варіант
if connected?, do: "Підключено", else: "Відключено"
# Присвоєння результату
message = if age >= 18 do
"Ви повнолітній"
else
"Ви неповнолітній"
end
# Без else (повертає nil)
if user_logged_in? do
show_dashboard()
end
false і nil вважаються хибними. Всі інші значення (включаючи 0, "", []) вважаються істинними.
unless
Конструкція unless — це протилежність if, виконується коли умова хибна:
# Базовий синтаксис
unless paid? do
send_invoice()
end
# З else (рідко використовується)
unless error do
IO.puts("Успіх")
else
IO.puts("Помилка")
end
# Однорядковий варіант
unless authenticated?, do: redirect_to_login()
# Зазвичай unless використовують без else
status = unless maintenance_mode? do
:online
end
unless з else, оскільки це зменшує читабельність. Краще використати if з інвертованою умовою.
case
Конструкція case дозволяє зіставляти значення з кількома зразками:
# Базове зіставлення
case File.read("config.json") do
{:ok, content} ->
parse_json(content)
{:error, :enoent} ->
"Файл не знайдено"
{:error, reason} ->
"Помилка: #{reason}"
end
# Зіставлення зі значеннями
result = case status do
:ok -> "Успішно"
:error -> "Помилка"
:pending -> "Очікування"
_ -> "Невідомий статус"
end
# З охоронцями (guards)
case number do
n when n < 0 -> "Від'ємне"
n when n == 0 -> "Нуль"
n when n > 0 -> "Додатне"
end
# Зіставлення зі складними структурами
case user do
%{role: :admin, active: true} ->
grant_full_access()
%{role: :user, active: true} ->
grant_limited_access()
%{active: false} ->
deny_access()
_ ->
:unknown
end
cond
Конструкція cond перевіряє кілька умов послідовно, подібно до else-if в інших мовах:
# Базовий синтаксис
grade = cond do
score >= 90 -> "A"
score >= 80 -> "B"
score >= 70 -> "C"
score >= 60 -> "D"
true -> "F" # default випадок
end
# Перевірка кількох умов
status = cond do
temperature > 30 -> :hot
temperature > 20 -> :warm
temperature > 10 -> :cool
temperature > 0 -> :cold
true -> :freezing
end
# Складні умови
message = cond do
is_nil(user) ->
"Користувач не знайдений"
not user.active ->
"Акаунт деактивовано"
user.subscription_expired? ->
"Підписка закінчилася"
true ->
"Ласкаво просимо!"
end
cond завжди потрібен випадок, що спрацює. Зазвичай останньою умовою ставлять true як default варіант.
with
Конструкція with ідеальна для послідовних операцій, які можуть завершитись помилкою:
# Базовий синтаксис
with {:ok, user} <- fetch_user(id),
{:ok, profile} <- fetch_profile(user),
{:ok, posts} <- fetch_posts(profile) do
{:ok, %{user: user, profile: profile, posts: posts}}
end
# З обробкою помилок через else
with {:ok, file} <- File.read("data.json"),
{:ok, data} <- JSON.decode(file),
{:ok, validated} <- validate(data) do
process(validated)
else
{:error, :enoent} ->
{:error, "Файл не знайдено"}
{:error, :invalid_json} ->
{:error, "Невалідний JSON"}
error ->
{:error, "Невідома помилка: #{inspect(error)}"}
end
# З проміжними обчисленнями
with {:ok, width} <- get_width(),
{:ok, height} <- get_height(),
area = width * height,
true <- area > 0 do
{:ok, area}
else
false -> {:error, "Площа повинна бути додатною"}
error -> error
end
# Практичний приклад: створення користувача
def create_user(params) do
with {:ok, validated} <- validate_params(params),
{:ok, hashed_password} <- hash_password(validated.password),
{:ok, user} <- insert_user(validated, hashed_password),
{:ok, _email} <- send_welcome_email(user) do
{:ok, user}
else
{:error, :validation_failed} = error ->
error
{:error, :duplicate_email} ->
{:error, "Email вже використовується"}
error ->
Logger.error("Помилка створення користувача: #{inspect(error)}")
{:error, "Не вдалося створити користувача"}
end
end
Порівняння конструкцій
| Конструкція | Призначення | Коли використовувати |
|---|---|---|
| if/else | Проста умова | Коли є одна умова для перевірки |
| unless | Негативна умова | Коли потрібно виконати код при хибній умові |
| case | Зіставлення зразків | Коли потрібно зіставити значення з кількома зразками |
| cond | Множинні умови | Коли є кілька різних умов для перевірки |
| with | Послідовні операції | Для ланцюжків операцій з обробкою помилок |
try, catch та rescue
Для обробки виключень використовуються конструкції try/catch/rescue:
# Обробка виключень через rescue
try do
risky_operation()
rescue
ArithmeticError ->
"Математична помилка"
RuntimeError ->
"Помилка виконання"
e in [ArgumentError, FunctionClauseError] ->
"Помилка аргументів: #{inspect(e)}"
end
# Catch для throw
try do
throw(:emergency_exit)
catch
:emergency_exit -> "Екстрене завершення"
value -> "Перехоплено: #{value}"
end
# З after для очищення ресурсів
{:ok, file} = File.open("data.txt", [:write])
try do
IO.write(file, "Hello, World!")
after
File.close(file)
end
# Повний приклад
result = try do
dangerous_operation()
rescue
e in RuntimeError ->
Logger.error("Runtime error: #{inspect(e)}")
{:error, :runtime_error}
catch
:exit, reason ->
Logger.error("Exit caught: #{inspect(reason)}")
{:error, :exit}
after
cleanup_resources()
end
try/rescue обережно!
Короткі форми (do:)
Всі конструкції можна писати в однорядковій формі:
# if/else
result = if valid?, do: :ok, else: :error
# unless
unless ready?, do: wait()
# case
status = case code, do: (200 -> :ok; 404 -> :not_found; _ -> :error)
# cond (рідко використовується в однорядковій формі)
result = cond do: (x > 0 -> :positive; true -> :other)
# Зазвичай однорядкову форму використовують для простих випадків
action = if authorized?, do: proceed(), else: halt()
Практичні поради
- if/unless: використовуйте для простих бінарних умов
- case: ідеальний для зіставлення зразків та обробки різних типів даних
- cond: використовуйте замість вкладених if/else для множинних умов
- with: незамінний для "railway-oriented programming" та обробки помилок
- Зіставлення у функціях: часто краще використовувати кілька визначень функції замість case всередині функції
- Уникайте вкладеності: використовуйте with або винесення логіки в окремі функції
Приклад комбінування конструкцій
defmodule UserService do
def process_registration(params) do
with {:ok, validated} <- validate(params),
{:ok, user} <- create_user(validated) do
case send_confirmation_email(user) do
{:ok, _} ->
{:ok, user}
{:error, reason} ->
Logger.warn("Email не надіслано: #{reason}")
{:ok, user} # Все одно повертаємо успіх
end
else
{:error, :invalid_email} ->
{:error, "Невалідний email"}
error ->
{:error, "Помилка реєстрації"}
end
end
defp validate(params) do
cond do
is_nil(params[:email]) ->
{:error, :invalid_email}
String.length(params[:password]) < 8 ->
{:error, :weak_password}
true ->
{:ok, params}
end
end
end
Правильне використання керуючих конструкцій робить код Elixir чистим, виразним та легким для розуміння. Обирайте конструкцію відповідно до контексту та складності логіки.
Коментарі
Дописати коментар