Elixir 中函数调用的思考
开始使用 Elixir 以来,对于函数调用的方式有了很多的想法,比如利用 Elixir 中的 pipeline 和函数调用写一个简单的例子:
defmodule Demo do
defstruct [:one, :two, :three]
def work(struct, fun \\ &(&1)) do
fun.(struct)
end
end
Demo
|> struct(%{})
|> work(&(%{&1 | one: :done}))
|> work(&(%{&1 | two: :done}))
|> work(&(%{&1 | three: :done}))
得益于 Elixir 中函数的 & 表示法,在写出简洁优雅的程序的同时,功能也异常强大。接下来我们就利用这些函数特性来实现更多的功能:
1. 使 [1, 2, 3] 通过一个函数调用实现 [1+3, 2+5, 3+8] 的效果
其实也就是实现 List 中每个元素调用不同的函数:
def zip(vars, funs) do
unless length(vars) == length(funs) do
raise "Variable list and function list should have the same length"
end
fun_var_list = Enum.zip(vars, funs)
Enum.map fun_var_list, fn({var, fun}) -> fun.(var) end
end
zip([1,2,3], [&(&1+3), &(&1+5), &(&1+8)])
2. 使 fn([1, 2, 3]) 实际调用 fn(1, 2, 3)
这个和 Ruby 中的 * 解构操作有点类似,即接受 List 参数,转变为多个参数, 在 Elixir 中我们使用 apply 来达到该功能
def splicing(vars, fun) do
apply(fun, vars)
end
3. 逐步补全函数调用的参数
在 Elixir 中,可以不一次性提供函数需要的参数,只提供部分参数,这样就会生成新的需要剩下参数才能调用的函数,比如:
iex(1)> a = fn (a, b, c) -> a + b + c end
#Function<18.52032458/3 in :erl_eval.expr/5>
iex(2)> b = &a.(1, &1, &2)
#Function<12.52032458/2 in :erl_eval.expr/5>
iex(3)> c = &b.(&1, 3)
#Function<6.52032458/1 in :erl_eval.expr/5>
iex(4)> d = c.(2)
6
我们也可以注意到,对于参数的顺序,也没有强制的要求,那么我们可以利用这个特性传递需要的数据或者参数来实现相应的功能,举个栗子:利用刚刚的这个特性对函数进行解构(这里要利用:erlang库):
defmodule Curry do
def decode(fun) do
{_, num} = :erlang.fun_info(fun, :arity)
decode(fun, num, [])
end
def decode(fun, 0, arguments) do
apply(fun, arguments |> Enum.reverse)
end
def decode(fun, num, arguments) do
fn arg ->
decode(fun, num - 1, [arg | arguments])
end
end
end
在 iex 中做一下测试:
iex(1)> f = Curry.decode(fn(a,b,c,d) -> a+b+c+d end)
#Function<0.95689008/1 in Curry.decode/3>
iex(2)> b = f.(1).(2)
#Function<0.95689008/1 in Curry.decode/3>
iex(3)> :erlang.fun_info(b)[:env]
[[2, 1], 2, #Function<4.52032458/4 in :erl_eval.expr/5>]
可以看到,我们实现了逐步添加参数的功能,同时,可以从函数中提取出需要的参数。
4. 函数在编译时不会检查结果
如果编写的代码需要调用一个在编译之后才存在的 Module,而在编译的时候直接调用会出现 Warn, 通过使用 fn -> do_thing end 匿名函数则可以避免 Warn。
这个类似特性在 Logger 中应用比较多,在打印 debug 信息时,尽量使用匿名函数。
暂时就这么多了。
5
Kudos
5
Kudos