我发现Kernel.apply/3
允许通过将方法指定为原子来动态调用模块中的公共方法,例如result = apply(__MODULE__, :my_method, [arg])
转换为result = my_method(arg)
令我困惑的是一种称私人方法的方法;给出这样的代码:
defmodule MyModule do
def do_priv(atom, args) when is_list(args) do
apply(__MODULE__, atom, args)
end
# (change defp => def, and this all works)
defp something_private(arg), do: arg #or whatever
end
我希望MyModule.do_priv(:something_private, [1])
是允许的,因为它是从模块内调用私有方法。我可以理解,在引擎盖下Elixir正在使用Erlang的apply / 3,所以这种方法可能不会让我们在那里。
我也尝试过使用Code.eval_quoted/3
方法,但它似乎无法调用硬编码的私有方法(因此没有时间花在构建AST上,而是而不是使用quote do
如下所示 - 尽管如果有人知道如何使这项工作,这是一个选项:
defmodule MyModule do
def do_priv_static do
something_private(1) #this works just fine
end
def do_priv_dynamic do
code = quote do
something_private(1)
end
Code.eval_quoted(code, [], __ENV__) #nope. fails
end
defp something_private(arg), do: arg #or whatever
end
同样,它可以从包含模块中访问私有函数,所以我希望它是允许的。我可能不理解__ENV__
eval_quoted
参数
目前唯一可行的解决方案是将defp
更改为def
,这对我的个人代码来说是一个很好的解决方案;但是既然我编写的代码支持其他关心的程序员,我想找到解决方案。
我对其他方法持开放态度,但我个人难以理解如何实现这一目标。
答案 0 :(得分:9)
在Erlang中无法动态调用私有函数(因此不能在Elixir中)。如果需要进行动态分派,可以考虑使用多子句功能。一个人为的例子(肯定是一个糟糕的例子,但想不出更好的ATM):
iex(1)> defmodule Math do
def operation(op) do
IO.puts "invoking #{inspect op}"
run_op(op)
end
defp run_op({:add, x, y}), do: x + y
defp run_op({:mul, x, y}), do: x * y
defp run_op({:square, x}), do: x * x
end
iex(2)> Math.operation({:add, 1, 2})
invoking {:add, 1, 2}
3
iex(3)> Math.operation({:mul, 3, 4})
invoking {:mul, 3, 4}
12
iex(4)> Math.operation({:square, 2})
invoking {:square, 2}
4
另一种选择是公开您的功能,但用@doc false
表示它们是内部的 - 即不打算由客户公开使用。您还可以考虑将这些函数移动到单独的模块,并将整个模块标记为@moduledoc false
作为内部模块。两种方法偶尔用于Elixir代码中。
但我建议启动简单,并使用模式匹配+多子句函数。如果代码变得更复杂,我会考虑其他选项。
答案 1 :(得分:9)
首先,您应该知道在MyModule模块中调用的f()
与在同一位置调用的MyModule.f()
不同。见http://www.erlang.org/doc/reference_manual/code_loading.html#id86422
您只能调用私有函数f()
样式。编译器也会检查这些调用 - 如果函数不存在,则会出现编译错误。当你在同一个地方使用MyModule.f()
时,你会不得到编译错误,因为这些调用只在运行时检查(即使你从内部调用模块),效果是(AFAIK)就像你从任何其他模块调用MyModule.f()
一样 - 在运行时查找模块,你只能调用导出的(公共)函数。
因此,您可以不以任何其他方式调用私有函数,而不仅仅是普通的f()
。 apply(mod,fun,[])
等效于mod.fun.()
样式 - 模块在运行时解析,私有函数无法访问。
您可以在此示例中自行尝试所有变体:https://gist.github.com/mprymek/3302ff9d13fb014b921b
您现在可以看到,在编译时必须始终知道对私有函数的调用,因此您甚至不能使用eval_quoted魔法或任何其他魔法来使它们“动态”......
Sasa Juric建议使用@doc false
是正确的解决方案。
答案 2 :(得分:2)
您可以使用Macros
动态调用同一模块中的私有方法。这是一个简单的宏,可以实现:
defmacro local_apply(method, args) when is_atom(method) do
quote do
unquote(method)(unquote_splicing(args))
end
end
要在你的模块中调用它,你可以这样做(只需记住在调用之前定义宏!):
def call_priv(some_argument) do
local_apply(:something_private, [some_argument])
end
defp something_private(arg), do: arg
调用时, local_apply
将扩展为所需的方法调用 - 但仅在编译时 - 这意味着您无法在运行时将宏动态扩展为函数调用。