【问题标题】:Elixir - Call private function dynamicallyElixir - 动态调用私有函数
【发布时间】:2015-05-09 21:25:24
【问题描述】:

我发现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

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

目前唯一可行的解​​决方案是将defp 更改为def,这是我个人代码的一个很好的解决方案;但由于我编写的代码支持其他关心的程序员,我想找到一个解决方案。

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

【问题讨论】:

    标签: elixir


    【解决方案1】:

    使用宏

    您可以使用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 会在调用时扩展为带有参数的所需方法调用 - 但仅限于编译时 - 这意味着您无法在运行时将宏动态扩展为函数调用。

    【讨论】:

      【解决方案2】:

      首先,您应该知道在 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 是正确的解决方案。

      【讨论】:

      • 顺便说一句,调用 my_module:nonexistent() 是完全正确的,因为代码可以被热重载,而这一行实际上可以调用 NEW 模块版本,它可以导出 nonexistent/0。
      【解决方案3】:

      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
      

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

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

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-05-16
      • 2015-05-18
      • 2018-10-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-22
      • 1970-01-01
      相关资源
      最近更新 更多