Elixir - 动态调用私有函数

Chr*_*yer 12 elixir

我发现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
Run Code Online (Sandbox Code Playgroud)

我希望这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
Run Code Online (Sandbox Code Playgroud)

同样,它可以从包含模块中访问私有函数,所以我希望它是允许的.它可能我只是不明白__ENV__参数eval_quoted

唯一可行的解决方案,现在正在改变defpdef,这是我个人的代码精解; 但是既然我编写的代码支持其他关心的程序员,我想找到一个解决方案.

我对其他方法持开放态度,但我个人难以理解如何实现这一目标.

sas*_*ric 9

在Erlang中不能动态调用AFAIK私有函数(因此不能在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
Run Code Online (Sandbox Code Playgroud)

另一个选择是公开您的功能,但表明@doc false它们是内部的 - 即不打算由客户公开​​使用.您还可以考虑将这些功能移动到单独的模块中,并将整个模块标记@moduledoc false为内部模块.两种方法偶尔用于Elixir代码中.

但是我建议开始简单,并使用模式匹配+多子句函数.如果代码变得更复杂,我会考虑其他选项.


Mir*_*mek 9

首先,您应该知道f()在MyModule模块中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是正确的解决方案.


She*_*yar 5

使用宏

您可以使用Macros动态调用同一模块内的私有方法。这是一个简单的宏,可以实现:

defmacro local_apply(method, args) when is_atom(method) do
  quote do
    unquote(method)(unquote_splicing(args))
  end
end
Run Code Online (Sandbox Code Playgroud)

要在您的模块中调用它,您可以这样做(请记住在调用之前定义宏!):

def call_priv(some_argument) do
  local_apply(:something_private, [some_argument])
end

defp something_private(arg), do: arg
Run Code Online (Sandbox Code Playgroud)

local_apply 会在调用时使用参数扩展到您想要的方法调用 - 但仅在编译时 - 这意味着您不能在运行时动态地将宏扩展到您的函数调用。