Анонімні функції (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)
Поширені помилки
# Неправильно
add = fn a, b -> a + b end
add(5, 3) # Помилка!
# Правильно
add.(5, 3) # OK
# Не працює - змінні незмінні!
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-ів та композиції функцій.
Коментарі
Дописати коментар