Исходный код Анонимные функции
Анонимные функции позволяют нам хранить и передавать исполняемый код так, как будто это целое число или строка. Давайте узнаем больше.
Идентификация функций и документация
Прежде чем перейти к обсуждению анонимных функций, давайте поговорим о том, как Elixir идентифицирует именованные функции.
Функции в Elixir идентифицируются по своему имени и арности. Арность функции описывает количество аргументов, которые принимает функция. В дальнейшем в документации мы будем использовать как имя функции, так и её арность для описания функций. trunc/1 идентифицирует функцию, которая называется trunc и принимает 1 аргумент, в то время как trunc/2 идентифицирует другую (несуществующую) функцию с тем же именем, но с арностью 2.
Мы также можем использовать этот синтаксис для доступа к документации. Оболочка Elixir определяет функцию h, которую вы можете использовать для доступа к документации любой функции. Например, ввод h trunc/1 выведет документацию для функции trunc/1:
iex> h trunc/1
def trunc(number)
Returns the integer part of number.
h trunc/1 работает, потому что она определена в модуле Kernel. Все функции в модуле Kernel автоматически импортируются в наш пространство имён. Чаще всего вы также будете включать имя модуля при поиске документации для данной функции:
iex> h Kernel.trunc/1
def trunc(number)
Returns the integer part of number.
Вы можете использовать модуль + функцию для поиска чего угодно, включая операторы (попробуйте h Kernel.+/2). Вызов h без аргументов отображает документацию для IEx.Helpers, где определены h и другие функциональности.
Определение анонимных функций
Анонимные функции в Elixir ограничиваются ключевыми словами fn и end:
iex> add = fn a, b -> a + b end #Function<12.71889879/2 in :erl_eval.expr/5>
В примере выше мы определили анонимную функцию, которая принимает два аргумента, a и b, и возвращает результат a + b. Аргументы всегда находятся слева от ->, а код, который должен быть выполнен, справа. Анонимная функция хранится в переменной add. Вы можете видеть, что она возвращает значение, представленное #Function<...>. Хотя её представление непрозрачно, часть :erl_eval.expr указывает на то, что функция была определена в оболочке (во время вычисления).
Мы можем вызывать анонимные функции, передавая аргументы ей, используя точку (.) между переменной и открывающей скобкой:
iex> add.(1, 2) 3
Точка делает очевидным, когда вы вызываете анонимную функцию, хранящуюся в переменной add, по сравнению с функцией, названной add/2. Например, если у вас есть анонимная функция, хранящаяся в переменной is_atom, нет неоднозначности между is_atom.(:foo) и is_atom(:foo). Если бы оба использовали тот же синтаксис is_atom(:foo), единственный способ узнать фактическое поведение is_atom(:foo) заключался бы в сканировании всего кода до этого момента, чтобы найти возможное определение переменной is_atom. Такое сканирование снижает поддерживаемость, так как разработчикам приходится отслеживать дополнительный контекст в уме при чтении и написании кода.
Анонимные функции в Elixir также идентифицируются по количеству аргументов, которые они принимают. Мы можем проверить, является ли значение функцией, используя is_function/1, а также проверить её арность с помощью is_function/2:
iex> is_function(add) true # check if add is a function that expects exactly 2 arguments iex> is_function(add, 2) true # check if add is a function that expects exactly 1 argument iex> is_function(add, 1) false
Замыкания
Анонимные функции также могут получить доступ к переменным, которые находятся в области видимости, когда функция определена. Это обычно называется замыканиями, так как они закрывают свою область видимости. Давайте определим новую анонимную функцию, которая использует функцию add , которую мы определили ранее:
iex> double = fn a -> add.(a, a) end #Function<6.71889879/1 in :erl_eval.expr/5> iex> double.(2) 4
Переменная, присвоенная внутри функции, не влияет на окружающую среду:
iex> x = 42 42 iex> (fn -> x = 0 end).() 0 iex> x 42
Условные выражения и условия
Аналогично case/2, мы можем сопоставлять шаблоны с аргументами анонимных функций, а также определять несколько условных выражений и условий:
iex> f = fn ...> x, y when x > 0 -> x + y ...> x, y -> x * y ...> end #Function<12.71889879/2 in :erl_eval.expr/5> iex> f.(1, 3) 4 iex> f.(-1, 3) -3
Количество аргументов в каждом условном выражении анонимной функции должно быть одинаковым, в противном случае возникает ошибка.
iex> f2 = fn ...> x, y when x > 0 -> x + y ...> x, y, z -> x * y + z ...> end ** (CompileError) iex:1: cannot mix clauses with different arities in anonymous functions
Оператор захвата
В этом руководстве мы использовали обозначение name/arity для обозначения функций. Оказывается, что это обозначение может фактически использоваться для захвата существующей функции в тип данных, который мы можем передавать, подобно тому, как ведут себя анонимные функции.
iex> fun = &is_atom/1 &:erlang.is_atom/1 iex> is_function(fun) true iex> fun.(:hello) true iex> fun.(123) false
Как вы видите, после захвата функции мы можем передать её как аргумент или вызвать её, используя обозначение анонимной функции. Возвращаемое значение выше также намекает на то, что мы можем захватывать функции, определённые в модулях:
iex> fun = &String.length/1
&String.length/1
iex> fun.("hello")
5
Поскольку операторы являются функциями в Elixir, вы также можете захватить операторы:
iex> add = &+/2 &:erlang.+/2 iex> add.(1, 2) 3
Синтаксис захвата также может использоваться в качестве сокращения для создания функций, которые оборачивают существующие функции. Например, представьте, что вы хотите создать анонимную функцию, которая проверяет, имеет ли заданная функция арность 2. Вы могли бы написать её как:
iex> is_arity_2 = fn fun -> is_function(fun, 2) end #Function<8.71889879/1 in :erl_eval.expr/5> iex> is_arity_2.(add) true
Но используя синтаксис захвата, вы можете написать её как:
iex> is_arity_2 = &is_function(&1, 2) #Function<8.71889879/1 in :erl_eval.expr/5> iex> is_arity_2.(add) true
&1 представляет собой первый аргумент, переданный в функцию. Поэтому обе анонимные функции is_arity_2 выше эквивалентны.
Ещё раз, учитывая, что операторы являются вызовами функций, сокращение синтаксиса захвата также работает с операторами или даже интерполяцией строк:
iex> fun = &(&1 + 1)
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> fun.(1)
2
iex> fun2 = &"Good #{&1}"
#Function<6.127694169/1 in :erl_eval.expr/5>
iex> fun2.("morning")
"Good morning"
&(&1 + 1) выше точно такое же, как fn x -> x + 1 end. Вы можете узнать больше об операторе захвата & в его документации.
Далее давайте пересмотрим некоторые типы данных, которые мы изучали ранее, и углубимся в их работу.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/anonymous-functions.html