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

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

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

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

Docker-compose для створення Postgresql бази даних

Docker Compose — це інструмент, який дозволяє визначати та запускати багатоконтейнерні Docker-застосунки. Замість того, щоб вручну запускати кожен контейнер із довгими командами docker run, docker-compose.yml надає простий спосіб описати всю архітектуру додатка у вигляді YAML-файлу. Це дозволяє легко створювати, запускати, зупиняти та масштабувати сервіси за допомогою однієї команди, що значно спрощує розробку, тестування та розгортання застосунків. Основні можливості Docker Compose включають: запуск кількох контейнерів одночасно, визначення мережі та спільних томів між контейнерами, налаштування змінних середовища та автоматичне підключення сервісів один до одного через імена сервісів. Він особливо корисний для локального середовища розробки, CI/CD-процесів і навіть невеликих продакшен-рішень, де потрібно швидко відтворити середовище для тестування або демонстрації. Мінімальний docker-compose.yml для локального використання PostgreSQL без збереження даних після видалення контейне...

Шпаргалка по запуску та збірці Spring Boot-проєктів

Maven + Spring Boot 1. Збірка проєкту (із завантаженням залежностей, компіляцією, запуском тестів та створенням артефакту) mvn clean install 2. Збірка артефакту без встановлення у локальний репозиторій mvn package 3. Збірка без тестів mvn clean package -DskipTests 4. Запуск Spring Boot-проєкту mvn spring-boot:run 5. Запуск із активним профілем Spring Boot mvn spring-boot:run -Dspring-boot.run.profiles=dev 6. Запуск із параметрами mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081 --spring.profiles.active=prod" 7. Запуск з jar-файлу java -jar target/your-app-name.jar 8. Запуск тестів mvn test 9. Запуск, якщо pom.xml у підкаталозі mvn -f шлях/до/pom.xml spring-boot:run 10. Запуск із Maven-профілем (не плутати з Spring Boot профілем) mvn clean install -P dev Gradle + Spring Boot 1. Збірка проєкту (з компіляцією, тестами та створенням jar) ...

Прості типи даних в 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 = ...