匿名函数允许我们存储和传递可执行代码,就像它是一个整数或字符串一样。让我们了解更多。
定义匿名函数
Elixir 中的匿名函数由关键字 fn 和 end 分隔:
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
在上面的例子中,我们定义了一个匿名函数,它接收两个参数 a 和 b,并返回 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>
... >f.(1, 3)
4
... >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 完全相同。您可以在其文档中阅读有关捕获运算符 & 的更多信息。
接下来,让我们重新回顾一下我们过去学习过的一些数据类型,并深入了解它们的工作原理。