Как работают анонимные функции (fn) и чем они отличаются от именованных?
В Elixir анонимные функции — это функции без имени, которые можно создать непосредственно в теле кода, присвоить переменной и вызывать как значение. Они создаются с использованием конструкции fn … -> … end, и по сути являются замыканиями (closures), то есть могут захватывать переменные из окружающего контекста.
Как создаются анонимные функции
Анонимная функция в Elixir создаётся с помощью ключевого слова fn. Пример:
sum = fn (a, b) -> a + b end
Это определяет переменную sum, которая ссылается на анонимную функцию, принимающую два аргумента и возвращающую их сумму. Вызов:
sum.(2, 3) # => 5
Для вызова анонимных функций используется синтаксис с точкой: имя_переменной.(аргументы).
Сравнение с именованными функциями
Признак | Анонимные функции (fn) | Именованные функции (def) |
---|---|---|
Определение | Через fn -> ... end | Через def имя(...) do ... end |
--- | --- | --- |
Имя | Нет | Есть |
--- | --- | --- |
Сфера действия | Значение переменной, может передаваться | Принадлежит модулю |
--- | --- | --- |
Вызов | fun.(args) | ModuleName.function_name(args) |
--- | --- | --- |
Видимость | Только в пределах переменной | В рамках модуля, может быть public |
--- | --- | --- |
Захват контекста (замыкание) | Да | Нет, кроме module attributes |
--- | --- | --- |
Примеры анонимных функций
Простая функция:
double = fn x -> x \* 2 end
double.(10) # => 20
Функция с несколькими выражениями:
greet = fn name ->
IO.puts("Привет, #{name}")
:ok
end
greet.("Алексей")
Паттерн-матчинг в аргументах:
case_fn = fn
{:ok, value} -> "Успешно: #{value}"
{:error, reason} -> "Ошибка: #{reason}"
end
case_fn.({:ok, 123})
Передача функции в Enum:
list = \[1, 2, 3\]
Enum.map(list, fn x -> x \* x end) # => \[1, 4, 9\]
Замыкания (Closures)
Анонимные функции могут "захватывать" переменные из окружающего контекста:
multiplier = 3
multiply = fn x -> x \* multiplier end
multiply.(5) # => 15
Здесь multiplier был взят из внешней области видимости — это пример замыкания. Такие функции удобны, когда необходимо сгенерировать поведение на лету с частично заданными параметрами.
Композиция и вложенные функции
Анонимные функции можно возвращать из других функций, передавать как аргументы или даже создавать внутри других функций:
defmodule Example do
def create_multiplier(factor) do
fn x -> x \* factor end
end
end
times_two = Example.create_multiplier(2)
times_two.(10) # => 20
Ограничения анонимных функций
-
Нельзя использовать guard-клаузы в теле fn (но можно внутри case, cond, with и т.д.).
-
Нельзя использовать def внутри анонимной функции.
-
Не имеют имени, поэтому не отображаются явно в стеке вызовов (tracing может быть сложнее).
-
Не могут быть вызваны напрямую из других модулей — только через переменную.
Практическое применение
- **Вызов в Enum, Stream, Task, Agent, GenServer.
** - **Гибкая передача поведения (например, колбэки).
**
Реализация стратегии обработки:
defmodule Strategy do
def execute(data, handler_fn) do
handler_fn.(data)
end
end
Strategy.execute("world", fn name -> "Hello, #{name}" end)
- **Моделирование частичных вычислений.
**
Компактная запись через &
Вместо fn x -> x * 2 end, можно использовать сокращённый синтаксис:
&(&1 \* 2)
Здесь &1, &2, &3 — это аргументы функции. Это особенно полезно в Enum.map:
Enum.map(\[1, 2, 3\], &(&1 + 10)) # => \[11, 12, 13\]
Такой подход уменьшает объём кода, но может быть менее читаемым при сложной логике.
Отличия по функциональности от именованных функций
-
Анонимные функции являются первоклассными объектами — можно передавать их как значения.
-
Именованные функции не могут быть легко переданы без использования &Module.function/arity.
-
Анонимные функции — независимые объекты, в то время как именованные всегда привязаны к модулю.