Как работают анонимные функции (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

Ограничения анонимных функций

  1. Нельзя использовать guard-клаузы в теле fn (но можно внутри case, cond, with и т.д.).

  2. Нельзя использовать def внутри анонимной функции.

  3. Не имеют имени, поэтому не отображаются явно в стеке вызовов (tracing может быть сложнее).

  4. Не могут быть вызваны напрямую из других модулей — только через переменную.

Практическое применение

  • **Вызов в 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.

  • Анонимные функции — независимые объекты, в то время как именованные всегда привязаны к модулю.