Elixir - 动态调用私有函数
Elixir - Call private function dynamically
我发现 Kernel.apply/3
允许通过将方法指定为原子来动态调用模块中的 public 方法,例如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
同样,它可以从包含的模块中访问私有函数,所以我希望它是允许的。可能我只是不理解 eval_quoted
的 __ENV__
参数
目前唯一有效的解决方案是将 defp
更改为 def
,这对我的个人代码来说是一个很好的解决方案;但是由于我编写的代码支持其他关心的程序员,所以我想找到一个解决方案。
我对其他方法持开放态度,但我个人对如何实现这一目标感到困惑。
AFAIK 动态调用私有函数在 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
另一种选择是使您的函数 public,但用 @doc false
表明它们是内部的 - 即不打算 public 仅供客户使用。您还可以考虑将此类功能移至单独的模块,并将整个模块 @moduledoc false
标记为内部模块。这两种方法偶尔会用在 Elixir 代码中。
不过,我建议从简单开始,使用模式匹配 + 多子句函数。如果代码变得更复杂,我会考虑其他选择。
首先,您应该知道在 MyModule 模块内部调用的 f()
与在同一位置调用的 MyModule.f()
不是一回事。参见 http://www.erlang.org/doc/reference_manual/code_loading.html#id86422
您只能调用私有函数 f()
风格。 编译器也会检查这些调用 - 如果该函数不存在,则会出现编译错误。当你在同一个地方使用 MyModule.f()
时,你会 not 得到编译错误,因为这些调用只在运行时检查(即使你是从它自己内部调用模块)和效果(AFAIK)与您从任何其他模块调用 MyModule.f()
相同 - 该模块在运行时查找,您只能调用导出的(public)函数。
因此,您可以不 以任何其他方式调用私有函数,而不仅仅是普通的f()
。 apply(mod,fun,[])
等同于 mod.fun.()
样式 - 模块在运行时解析,私有函数不可访问。
您可以在本例中自行尝试所有变体:https://gist.github.com/mprymek/3302ff9d13fb014b921b
您现在可以看到,在编译时必须始终知道对私有函数的调用,因此您甚至不能使用 eval_quoted 魔法或任何其他魔法来使它们 "dynamic".. .
Sasa Juric 使用 @doc false
的建议是正确的解决方案。
使用宏
您可以使用 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
会在调用时扩展到您想要的带有参数的方法调用 - 但仅在编译时 - 这意味着您无法在运行时将宏动态扩展到您的函数调用。
我发现 Kernel.apply/3
允许通过将方法指定为原子来动态调用模块中的 public 方法,例如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
同样,它可以从包含的模块中访问私有函数,所以我希望它是允许的。可能我只是不理解 eval_quoted
__ENV__
参数
目前唯一有效的解决方案是将 defp
更改为 def
,这对我的个人代码来说是一个很好的解决方案;但是由于我编写的代码支持其他关心的程序员,所以我想找到一个解决方案。
我对其他方法持开放态度,但我个人对如何实现这一目标感到困惑。
AFAIK 动态调用私有函数在 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
另一种选择是使您的函数 public,但用 @doc false
表明它们是内部的 - 即不打算 public 仅供客户使用。您还可以考虑将此类功能移至单独的模块,并将整个模块 @moduledoc false
标记为内部模块。这两种方法偶尔会用在 Elixir 代码中。
不过,我建议从简单开始,使用模式匹配 + 多子句函数。如果代码变得更复杂,我会考虑其他选择。
首先,您应该知道在 MyModule 模块内部调用的 f()
与在同一位置调用的 MyModule.f()
不是一回事。参见 http://www.erlang.org/doc/reference_manual/code_loading.html#id86422
您只能调用私有函数 f()
风格。 编译器也会检查这些调用 - 如果该函数不存在,则会出现编译错误。当你在同一个地方使用 MyModule.f()
时,你会 not 得到编译错误,因为这些调用只在运行时检查(即使你是从它自己内部调用模块)和效果(AFAIK)与您从任何其他模块调用 MyModule.f()
相同 - 该模块在运行时查找,您只能调用导出的(public)函数。
因此,您可以不 以任何其他方式调用私有函数,而不仅仅是普通的f()
。 apply(mod,fun,[])
等同于 mod.fun.()
样式 - 模块在运行时解析,私有函数不可访问。
您可以在本例中自行尝试所有变体:https://gist.github.com/mprymek/3302ff9d13fb014b921b
您现在可以看到,在编译时必须始终知道对私有函数的调用,因此您甚至不能使用 eval_quoted 魔法或任何其他魔法来使它们 "dynamic".. .
Sasa Juric 使用 @doc false
的建议是正确的解决方案。
使用宏
您可以使用 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
会在调用时扩展到您想要的带有参数的方法调用 - 但仅在编译时 - 这意味着您无法在运行时将宏动态扩展到您的函数调用。