Перейти до основного вмісту

Керуючі конструкції в Elixir

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
Примітка: В Elixir тільки 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
Увага: В Elixir філософія "Let it crash" — краще дозволити процесу впасти і перезапуститися, ніж обробляти всі можливі помилки. Використовуйте 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 чистим, виразним та легким для розуміння. Обирайте конструкцію відповідно до контексту та складності логіки.

Коментарі

Популярні публікації

Angular CLI

CLI (command-line interface) – інтерфейс командного рядка. Перед початком роботи має бути встановлений Node.js Встановлення: npm install -g @angular/cli Отримання допомоги: ng help Буде приблизно такий результат: add Adds support for an external library to your project. analytics Configures the gathering of Angular CLI usage metrics. See https://angular.io/cli/usage-analytics-gathering. build (b) Compiles an Angular app into an output directory named dist/ at the given output path. Must be executed from within a workspace directory. deploy Invokes the deploy builder for a specified project or for the default project in the workspace. config Retrieves or sets Angular configuration values in the angular.json file for the workspace. doc (d) Opens the official Angular documentation (angular.io) in a browser, and searches for a given keyword. e2e (e) Builds and serves an Angular app, then runs end-to-end tests. extract-i18n (i18n-extract, xi18n) Extracts i18n mes...

Створення нового Elixir-проєкту

Для створення новго Elixir-проєкту можна використати команду mix new first_project --sup Зрозуміло, що Elixir має бути встановлений раніше. Пояснення команди: mix — це вбудований інструмент для управління проєктами в Elixir (аналог maven у Java чи npm у JavaScript ). new — підкоманда mix, яка створює новий проєкт. first_project — назва твого нового проєкту. Папка з цією назвою буде створена у поточному каталозі. --sup — опціональний прапорець, який додає шаблон структури з Supervision Tree. Це означає, що створений проєкт одразу буде мати структуру, яка підтримує супервізор (супервізор керує життєвим циклом процесів у системі, перезапускаючи їх при падінні). Щоб створити файл з тестом, можна запустити команду із директорії проєкту mix test Приблизний вигляд структури проєкту:

Агрегати в DDD

Domain-Driven Design (DDD, предметно-орієнтоване проєктування) — це підхід до розробки програмного забезпечення, який зосереджується на моделюванні бізнес-логіки на основі реального домену (предметної області). Його запропонував Ерік Еванс у своїй книзі "Domain-Driven Design: Tackling Complexity in the Heart of Software". Основні принципи DDD Фокус на домені – головна увага приділяється предметній області, а не технічним деталям. Єдина мова (Ubiquitous Language) – розробники, бізнес-аналітики та інші учасники проєкту використовують спільну термінологію, щоб уникнути непорозумінь. Бізнес-логіка відокремлена від технічної реалізації – код моделюється так, щоб він чітко відображав реальний бізнес-процес. Основні концепції DDD Entity (Сутність) – об’єкт з унікальним ідентифікатором, що зберігається в системі (наприклад, Користувач, Замовлення). Value Object (Об’єкт-значення) – об’єкт, який не має унікального ідентифікатора та є незмінним (наприклад, Адреса або Гроші)...

Основи Elixir

Elixir — це функційна мова програмування, яка працює на віртуальній машині Erlang (BEAM). Вона призначена для створення масштабованих і відмовостійких систем. Elixir успадкував багато переваг Erlang, таких як легкість паралельного програмування та висока доступність, але також додав сучасний синтаксис та інструменти для розробки. Основні концепції Elixir Elixir є функційною мовою, тому вона орієнтована на використання функцій та незмінних даних. Ось декілька ключових концепцій: Незмінність даних. Усі дані в Elixir є незмінними, що спрощує роботу з паралельними процесами. Функції. Функції є основним будівельним блоком програми. Вони можуть бути анонімними або іменованими. Паттерн-матчинг. Elixir використовує паттерн-матчинг для роботи з даними, що дозволяє легко розбирати структури даних. Процеси. Elixir використовує легкі процеси для паралельного виконання завдань. Ці процеси ізольовані та спілкуються через передачу повідомлень. Синтаксис Elixir Синтаксис Elixir є прос...

Стратегії ребалансування в Kafka

Стратегії ребалансування в Kafka Ребалансування (Rebalancing) — це процес перерозподілу партицій між споживачами (сonsumer) у групі (Consumer Group). Kafka має кілька стратегій ребалансування: RangeAssignor. Ця стратегія розподіляє партиції на основі діапазонів, які створюються відповідно до сортування топіків і партицій. Наприклад, якщо є два консюмери і 6 партицій (P0–P5), перший консюмер отримає P0–P2, а другий — P3–P5. Особливості: Простий алгоритм. Може призводити до нерівномірного розподілу, якщо кількість партицій не ділиться порівну між консюмерами. RoundRobinAssignor. Ця стратегія рівномірно розподіляє партиції між консюмерами за круговим принципом. Наприклад, якщо є два консюмери і 6 партицій, перший отримає P0, P2, P4, а другий — P1, P3, P5. Особливості: Гарантує більш рівномірний розподіл партицій. Використовується в багатотопікових сценаріях. StickyAssignor. Ця стратегія намагається мінімізувати кількість змін у розподілі партицій між консюмерами при ре...