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

Анонімні функції в Elixir

Анонімні функції (lambda-функції) — це функції без імені, які можуть бути присвоєні змінним, передані як аргументи або повернуті з інших функцій. Вони є основою функціонального програмування в Elixir.

Базовий синтаксис

Анонімні функції створюються за допомогою ключових слів fn та end:

# Проста анонімна функція
add = fn a, b -> a + b end

# Виклик анонімної функції (з крапкою!)
result = add.(5, 3)
# result = 8

# Функція без параметрів
greet = fn -> "Привіт!" end
greet.()
# "Привіт!"

# Багаторядкова функція
calculate = fn x, y ->
  sum = x + y
  product = x * y
  {sum, product}
end

calculate.(4, 5)
# {9, 20}
Важливо: Для виклику анонімних функцій використовується крапка . перед дужками: func.(args). Це відрізняє їх від іменованих функцій.

Скорочений синтаксис (&)

Elixir пропонує скорочений синтаксис для простих анонімних функцій за допомогою оператора &:

# Звичайний синтаксис
add = fn a, b -> a + b end

# Скорочений синтаксис
add = &(&1 + &2)

# &1, &2, &3 ... — це позиційні параметри
multiply = &(&1 * &2)
multiply.(4, 5)
# 20

# Функція з одним параметром
square = &(&1 * &1)
square.(7)
# 49

# Виклик існуючої функції
# Замість: fn str -> String.upcase(str) end
upcase = &String.upcase/1
upcase.("hello")
# "HELLO"

# Часткове застосування
divide_by_two = &(&1 / 2)
divide_by_two.(10)
# 5.0

Зіставлення зразків в анонімних функціях

Анонімні функції підтримують зіставлення зразків так само, як іменовані функції:

# Множинні визначення (clauses)
handle_result = fn
  {:ok, result} -> "Успіх: #{result}"
  {:error, reason} -> "Помилка: #{reason}"
  _ -> "Невідомий результат"
end

handle_result.({:ok, "Дані завантажено"})
# "Успіх: Дані завантажено"

handle_result.({:error, "Не знайдено"})
# "Помилка: Не знайдено"

# Зіставлення зі списками
process_list = fn
  [] -> "Порожній список"
  [head | tail] -> "Перший: #{head}, Решта: #{inspect(tail)}"
end

process_list.([1, 2, 3])
# "Перший: 1, Решта: [2, 3]"

# Зіставлення з картами
get_name = fn
  %{name: name, age: age} when age >= 18 -> 
    "#{name} (дорослий)"
  %{name: name} -> 
    "#{name} (неповнолітній)"
  _ -> 
    "Невідомий"
end

get_name.(%{name: "Іван", age: 25})
# "Іван (дорослий)"

Замикання (Closures)

Анонімні функції можуть захоплювати змінні з зовнішньої області видимості:

# Захоплення зовнішньої змінної
multiplier = 5
multiply_by = fn x -> x * multiplier end
multiply_by.(3)
# 15

# Створення функцій з параметрами
defmodule Math do
  def create_multiplier(factor) do
    fn x -> x * factor end
  end
end

times_three = Math.create_multiplier(3)
times_three.(10)
# 30

times_five = Math.create_multiplier(5)
times_five.(10)
# 50

# Приклад з лічильником
defmodule Counter do
  def create_counter(initial \\ 0) do
    fn -> initial = initial + 1 end
  end
end

# Практичний приклад: конфігурація
defmodule Logger do
  def create_logger(prefix) do
    fn message -> 
      IO.puts("[#{prefix}] #{message}")
    end
  end
end

error_log = Logger.create_logger("ERROR")
info_log = Logger.create_logger("INFO")

error_log.("Щось пішло не так")
# [ERROR] Щось пішло не так
info_log.("Операція успішна")
# [INFO] Операція успішна

Передача функцій як аргументів

Анонімні функції часто передаються як аргументи до функцій вищого порядку:

# Enum.map з анонімною функцією
list = [1, 2, 3, 4, 5]

Enum.map(list, fn x -> x * 2 end)
# [2, 4, 6, 8, 10]

# Зі скороченим синтаксисом
Enum.map(list, &(&1 * 2))
# [2, 4, 6, 8, 10]

# Enum.filter
Enum.filter(list, fn x -> rem(x, 2) == 0 end)
# [2, 4]

Enum.filter(list, &(rem(&1, 2) == 0))
# [2, 4]

# Enum.reduce
Enum.reduce(list, 0, fn x, acc -> x + acc end)
# 15

# З іменованою функцією через &
Enum.map(["hello", "world"], &String.upcase/1)
# ["HELLO", "WORLD"]

# Ланцюжки операцій
[1, 2, 3, 4, 5]
|> Enum.filter(&(&1 > 2))
|> Enum.map(&(&1 * 3))
|> Enum.reduce(0, &(&1 + &2))
# 36

Анонімні функції в модулях Enum та Stream

# Enum.each - виконання для кожного елемента
Enum.each([1, 2, 3], fn x -> 
  IO.puts("Число: #{x}")
end)

# Enum.all? - перевірка всіх елементів
Enum.all?([2, 4, 6], fn x -> rem(x, 2) == 0 end)
# true

# Enum.any? - перевірка хоча б одного
Enum.any?([1, 2, 3], fn x -> x > 5 end)
# false

# Enum.find - пошук елемента
Enum.find([1, 2, 3, 4], fn x -> x > 2 end)
# 3

# Enum.group_by - групування
words = ["apple", "ant", "bear", "bee", "cat"]
Enum.group_by(words, fn word -> 
  String.first(word) 
end)
# %{"a" => ["apple", "ant"], "b" => ["bear", "bee"], "c" => ["cat"]}

# Stream для ледачих обчислень
Stream.map([1, 2, 3], fn x -> 
  IO.puts("Обробка #{x}")
  x * 2
end)
|> Enum.take(2)
# Обробка 1
# Обробка 2
# [2, 4]

Анонімні функції з кількома тілами (Multi-clause)

# Функція з охоронцями
classify = fn
  x when x < 0 -> :negative
  0 -> :zero
  x when x > 0 -> :positive
end

classify.(-5)  # :negative
classify.(0)   # :zero
classify.(10)  # :positive

# Обробка різних типів даних
processor = fn
  x when is_integer(x) -> 
    "Ціле число: #{x}"
  x when is_float(x) -> 
    "Дробове число: #{x}"
  x when is_binary(x) -> 
    "Рядок: #{x}"
  x when is_list(x) -> 
    "Список з #{length(x)} елементами"
  _ -> 
    "Невідомий тип"
end

processor.(42)        # "Ціле число: 42"
processor.(3.14)      # "Дробове число: 3.14"
processor.("Hello")   # "Рядок: Hello"
processor.([1,2,3])   # "Список з 3 елементами"

# Рекурсивна анонімна функція
factorial = fn
  0, _fun -> 1
  n, fun -> n * fun.(n - 1, fun)
end

factorial.(5, factorial)
# 120
Примітка: Для рекурсії в анонімних функціях потрібно передавати функцію саму собі як аргумент, оскільки вона не має імені.

Захоплення іменованих функцій

Можна створювати анонімні функції з іменованих за допомогою &:

# Захоплення функції модуля
upcase = &String.upcase/1
upcase.("hello")
# "HELLO"

# Захоплення з кількома аргументами
split = &String.split/2
split.("a,b,c", ",")
# ["a", "b", "c"]

# Використання в Enum
["hello", "world"] |> Enum.map(&String.upcase/1)
# ["HELLO", "WORLD"]

# Захоплення власної функції
defmodule Calculator do
  def add(a, b), do: a + b
  def subtract(a, b), do: a - b
end

add_func = &Calculator.add/2
add_func.(5, 3)
# 8

# Часткове застосування з &
add_five = &Calculator.add(5, &1)
add_five.(10)
# 15

Практичні приклади

# Валідація даних
validate_user = fn user ->
  cond do
    is_nil(user.email) -> {:error, "Email обов'язковий"}
    String.length(user.password) < 6 -> {:error, "Пароль занадто короткий"}
    true -> {:ok, user}
  end
end

# Трансформація даних
transform_users = fn users ->
  users
  |> Enum.filter(&(&1.active))
  |> Enum.map(fn user ->
    %{
      id: user.id,
      name: String.upcase(user.name),
      role: user.role
    }
  end)
  |> Enum.sort_by(&(&1.name))
end

# Створення конфігурації
defmodule ApiClient do
  def create_client(base_url, token) do
    %{
      get: fn path -> 
        HTTPoison.get("#{base_url}#{path}", [
          {"Authorization", "Bearer #{token}"}
        ])
      end,
      post: fn path, body ->
        HTTPoison.post("#{base_url}#{path}", body, [
          {"Authorization", "Bearer #{token}"},
          {"Content-Type", "application/json"}
        ])
      end
    }
  end
end

client = ApiClient.create_client("https://api.example.com", "secret123")
client.get.("/users")

# Композиція функцій
compose = fn f, g ->
  fn x -> f.(g.(x)) end
end

double = &(&1 * 2)
increment = &(&1 + 1)

double_then_increment = compose.(increment, double)
double_then_increment.(5)
# 11 (5 * 2 = 10, 10 + 1 = 11)

Поширені помилки

Помилка 1: Забування крапки при виклику
# Неправильно
add = fn a, b -> a + b end
add(5, 3)  # Помилка!

# Правильно
add.(5, 3)  # OK
Помилка 2: Спроба змінити захоплену змінну
# Не працює - змінні незмінні!
counter = 0
increment = fn -> counter = counter + 1 end

# Правильно - використовуйте процеси або Agent
{:ok, agent} = Agent.start_link(fn -> 0 end)
increment = fn -> Agent.update(agent, &(&1 + 1)) end

Переваги анонімних функцій

  • Гнучкість: можна створювати функції "на льоту"
  • Замикання: доступ до зовнішнього контексту
  • Функції вищого порядку: легко передавати як аргументи
  • Композиція: зручно комбінувати прості функції в складні
  • Читабельність: код стає більш виразним з Enum та Stream
  • Інкапсуляція: логіка залишається локальною

Анонімні функції — це потужний інструмент у Elixir, що дозволяє писати елегантний і виразний функціональний код. Вони особливо корисні при роботі з колекціями, створенні callback-ів та композиції функцій.

Коментарі

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

Шпаргалка по базових командах 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 = ...