查看源代码 匿名函数

匿名函数允许我们将可执行代码存储和传递,就像整数或字符串一样。让我们来进一步了解。

定义匿名函数

Elixir 中的匿名函数由关键字 fnend 括起来

iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true

在上面的示例中,我们定义了一个匿名函数,它接收两个参数 ab,并返回 a + b 的结果。参数始终位于 -> 的左侧,要执行的代码位于右侧。匿名函数存储在变量 add 中。

我们可以通过向匿名函数传递参数来调用它们。请注意,变量和括号之间需要一个点 (.) 来调用匿名函数。点明确表示您正在调用存储在变量 add 中的匿名函数,而不是调用名为 add/2 的函数。例如,如果您有一个存储在变量 is_atom 中的匿名函数,那么 is_atom.(:foo)is_atom(:foo) 之间就没有歧义。如果两者都使用相同的 is_atom(:foo) 语法,那么唯一知道 is_atom(:foo) 的实际行为的方法是扫描到目前为止的所有代码,以寻找 is_atom 变量的可能定义。这种扫描会影响可维护性,因为它要求开发人员在阅读和编写代码时跟踪额外的上下文。

Elixir 中的匿名函数也由它们接收的参数数量来识别。我们可以使用 is_function/2 检查函数是否具有给定的元数。

# 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

您还可以捕获运算符

iex> add = &+/2
&:erlang.+/2
iex> add.(1, 2)
3

捕获语法也可以用作创建函数的快捷方式。当您想要创建主要包装现有函数或运算符的函数时,这非常方便

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 + 1)fn x -> x + 1 end 完全相同。您可以在 其文档 中了解更多关于捕获运算符 & 的信息。

接下来,让我们回顾一下过去学习的一些数据类型,并深入了解它们的运作方式。