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,这对我的个人代码来说是一个很好的解决方案;但是由于我编写的代码支持其他关心的程序员,所以我想找到一个解决方案。

我对其他方法持开放态度,但我个人对如何实现这一目标感到困惑。

A​​FAIK 动态调用私有函数在 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 会在调用时扩展到您想要的带有参数的方法调用 - 但仅在编译时 - 这意味着您无法在运行时将宏动态扩展到您的函数调用。