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

Керуючі конструкції в 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

1. Підключення до PostgreSQL через командний рядок: psql -h <host> -p <port> -U <username> -d <database> 2. Підключення до бази без параметрів (якщо користувач і база мають однакове ім’я): psql 3. Показати список усіх баз даних: \l 4. Підключитися до іншої бази даних: \c <database_name> 5. Показати список таблиць у поточній базі: \dt 6. Показати всі об'єкти (таблиці, індекси, секвенції): \d 7. Показати таблиці з усіх схем: \dt *.* 8. Переглянути структуру конкретної таблиці: \d <table_name> 9. Виконати SQL-запит (приклад): SELECT * FROM users; 10. Вийти з psql: \q 11. Створити нову базу даних: CREATE DATABASE mydb; 12. Створити нову таблицю: CREATE TABLE users ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE ); 13. Додати новий запис: INSERT INTO users (name, email) VALUES ('Іван', 'ivan@example.com'); 14. Оновити дані в таблиці: UPDATE users SET name = 'Петро' WH...

Основи GLSL

Що таке GLSL? GLSL (OpenGL Shading Language) – мова програмування шейдерів для OpenGL. Використовується для написання vertex, fragment, geometry та інших шейдерів, що працюють на GPU. Оголошення версії #version 330 core Вказує версію GLSL. Наприклад, 330 core відповідає OpenGL 3.3. Вхідні та вихідні змінні layout(location=0) in vec3 aPos; layout(location=1) in vec3 aNormal; out vec3 FragPos; in – вхідні атрибути (vertex shader). out – вихідні змінні (vertex shader) або фінальний колір (fragment shader). Основні типи даних float, int, bool vec2, vec3, vec4 mat2, mat3, mat4 sampler2D (текстури) Тип Опис Приклади використання vec2 Двокомпонентний вектор з типом float. - Текстурні координати (UV) - 2D позиції - Швидкість у 2D vec3 Трикомпонентний вектор з типом float. - Координати позицій у 3D - Нормалі - Колір у форматі RGB vec4 Чотирикомпо...

Атоми в мові програмування Elixir

Атоми в Elixir Атоми є фундаментальною концепцією в Elixir , що відіграє ключову роль у створенні надійних та масштабованих систем. В Elixir це специфічний тип даних, який є константою , незмінною , ідентифікованою за своїм ім'ям . Отже, атом в Elixir — це іменована константа, що представляє себе. Уявіть, що ви даєте унікальне ім'я певній речі, і це ім'я завжди посилається саме на цю річ, і ніколи на щось інше. Наприклад, атом :ok завжди буде означати саме успішне завершення операції, а не якесь інше значення. Технічно, атоми є похідними від чисел . Кожен унікальний атом зберігається у таблиці атомів, і йому присвоюється унікальний цілочисельний ідентифікатор. Це робить їх надзвичайно ефективними для порівняння: замість порівняння рядків (що є повільною операцією), Elixir порівнює цілочисельні ідентифікатори. Переваги та особливості використання атомів Переваги атомів: Ефективність. Завдяки своєму числовому представленню, порівняння атомів є дуже швидким. Це осо...

Встановлення PostgreSQL на Ubuntu-сервер

Встановлення Оновлюємо пакети та встановлюємо PostgreSQL: sudo apt update sudo apt install -y postgresql postgresql-contrib Перевіряємо статус сервісу: sudo systemctl status postgresql Якщо PostgreSQL не запущений, запустимо його: sudo systemctl start postgresql sudo systemctl enable postgresql Налаштування безпеки Зміна пароля: sudo -u postgres psql У консолі PostgreSQL: ALTER USER postgres PASSWORD 'міцний_пароль'; \q \q - вихід з консолі. Список основних команд для роботи з PostgreSQL можна переглянути за посиланням. За замовчуванням PostgreSQL слухає localhost (127.0.0.1). Щоб дозволити доступ із зовнішніх машин, редагуємо конфігурацію: sudo nano /etc/postgresql/17/main/postgresql.conf (замість 17 вкажи версію PostgreSQL, яку встановлено) Шукаємо рядок: #listen_addresses = 'localhost' та замінюємо на listen_addresses = '*' Зберігаємо (Ctrl + X, Y, Enter). Тепер редагуємо pg_hba.conf: sudo nano /etc/postgresql/17/main/pg_hba.conf...

Прості типи даних в Elixir

Мова Elixir має низку простих (примітивних) типів даних, які часто використовуються в повсякденному програмуванні. Числа Elixir підтримує цілі (integer) та дійсні числа (float). # Цілі числа a = 42 b = -7 # Дійсні числа c = 3.14 d = -0.001 Булеві значення Elixir має два булевих значення: true та false . x = true y = false z = x and y # false Атоми Атоми — це константи з іменем, що починається з двокрапки. Вони широко використовуються, наприклад, для імен параметрів або станів. :ok :error :running :elixir_is_fun Рядки Рядки в Elixir — це двійкові дані з кодуванням UTF-8, оголошуються в подвійних лапках. name = "Pavlo" greeting = "Привіт, #{name}!" Nil Nil — це спеціальне значення, що позначає "відсутність значення". value = nil is_nil(value) # true Бінарні дані та байти Бінарні дані оголошуються в подвійних лапках або як бінарні літерали. string = "Привіт" # це рядок, але також бінарні дані binary = ...