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

Керуючі конструкції в 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 чистим, виразним та легким для розуміння. Обирайте конструкцію відповідно до контексту та складності логіки.

Коментарі

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

Дамп бази даних Postgresql локально

Що потрібно встановити локально (Ubuntu) Встанови клієнтські утиліти PostgreSQL: pg_dump , psql , pg_restore . sudo apt update sudo apt install postgresql-client Кроки міграції Ідея: не зберігати дамп на серверах. Зробити дамп з віддаленого сервера на свій локальний ПК, потім передати його на новий сервер і там відновити. 1) Створення дампу з віддаленого сервера локально pg_dump -h OLD_SERVER_IP -U OLD_USER -Fc -d OLD_DB -f dump_file.dump Пояснення ключів: -h (хост), -U (користувач), -Fc (custom format), -d (БД), -f (вихідний файл локально). 1.1) Варіант з розбиттям на частини (якщо треба) pg_dump -h OLD_SERVER_IP -U OLD_USER -Fc -d OLD_DB | split -b 10G - dump_file.dump.part_ Це створить локально файли типу dump_file.dump.part_aa , dump_file.dump.part_ab тощо. 2) Передача дампу на новий сервер Один файл: scp dump_file.dump NEW_USER@NEW_SERVER_IP:/tmp/ Або частинами: scp dump_file.dump.part_* NEW_USER@NEW_SERVER_IP:/tmp/ 3) Збирання файл...

Основи Next.js

Що таке Next.js? Next.js — це прогресивний фреймворк для розробки веб-застосунків на основі React. Він розширює можливості React, додаючи готову інфраструктуру для Server-Side Rendering (SSR), генерації статичних сторінок, маршрутизації через файлову систему, а також можливість створювати backend-ендпоінти прямо в проєкті. Чому він став популярним? Next.js швидко здобув популярність завдяки тому, що пропонує: SEO-дружні сторінки завдяки SSR / SSG; швидкий перший рендер (First Page Load); просту маршрутизацію без налаштування; розгортання в один клік через Vercel; вбудовану оптимізацію зображень та продуктивності. Чим відрізняється від класичного React У класичному React ми отримуємо лише клієнтську бібліотеку для побудови UI-компонентів. Усе інше (SSR, маршрути, API) треба налаштовувати вручну або через сторонні бібліотеки (наприклад, React Router, Redux, Express, etc.). Next.js — це уже повноцінний фреймворк. Він одразу має: серверний рендеринг...

ООП у TypeScript

Короткий вступ TypeScript додає статичну типізацію поверх JavaScript і підтримує класичні концепти ООП: класи , успадкування , інтерфейси , інкапсуляцію , поліморфізм та абстракції . Нижче — практичні приклади та пояснення. Класи — декларація, конструктор, властивості та методи class Person { public name: string; private age: number; protected email?: string; // необов’язкове static species = 'Homo sapiens'; constructor(name: string, age: number, email?: string) { this.name = name; this.age = age; this.email = email; } public greet(): string { return `Hi, I'm ${this.name}.`; } private getBirthYear(currentYear: number): number { return currentYear - this.age; } } const p = new Person('Ivan', 30, 'ivan@example.com'); console.log(p.greet()); // console.log(p.age); // помилка: 'age' приватне console.log(Person.species); Успадкування і перевизначення (extends, super) class Employee extends Person { ...

Створення проєкту на TypeScript, модулі

Крок 1. Ініціалізація проєкту Спочатку створимо нову теку та ініціалізуємо Node.js-проєкт за допомогою npm : mkdir my-typescript-app cd my-typescript-app npm init -y Крок 2. Встановлення TypeScript Додаємо TypeScript як dev-залежність: npm install --save-dev typescript ts-node @types/node - typescript — компілятор TypeScript - ts-node — дозволяє запускати TS-файли без попередньої компіляції - @types/node — типи для стандартних Node.js API Крок 3. Створення файлу tsconfig.json Ініціалізуємо конфігурацію TypeScript: npx tsc --init Приклад файлу tsconfig.json { "compilerOptions": { "target": "ES2020", // Версія JS у вихідному коді "module": "CommonJS", // Система модулів (для Node.js) "rootDir": "src", // Вхідна папка з TypeScript-кодом "outDir": "dist", // Куди компілюється код JS "str...

Інструменти для роботи з Node.js

Що таке npm? npm (Node Package Manager) — це офіційний пакетний менеджер для Node.js . Він дозволяє: Встановлювати сторонні бібліотеки та фреймворки Керувати залежностями проєкту Запускати скрипти (команди) через package.json Приклад ініціалізації проєкту з npm npm init -y Файл package.json (скорочений приклад) { "name": "my-project", "version": "1.0.0", "scripts": { "start": "ts-node src/index.ts", "build": "tsc" }, "dependencies": { "express": "^4.18.0" }, "devDependencies": { "typescript": "^5.0.0", "ts-node": "^10.0.0" } } Що таке tsconfig.json? tsconfig.json — це файл конфігурації для компілятора TypeScript, який визначає, як слід компілювати код. Приклад файлу tsconfig.json { "compilerOptions": { "target": "ES2020...