【问题标题】:Elixir - Call method on module by String-nameElixir - 通过字符串名称调用模块上的方法
【发布时间】:2016-08-09 07:54:01
【问题描述】:

我对 Elixir 和函数式编程语言很陌生。

在 Elixir 中,我想调用模块上的一个特定函数,给定模块名称为字符串。

我有以下(非常糟糕的)代码可以正常工作,这几乎可以满足我的要求:

module_name = elem(elem(Code.eval_file("module.ex", __DIR__), 0), 1)
apply(module_name, :helloWorld, [])

这(至少据我了解)编译当前目录中module.ex 的(已经编译的)模块。我正在从两个元组中提取模块名称(不是字符串,不知道它实际上是什么数据类型)并在其上运行方法helloWorld

这段代码有两个问题:

  1. 它会打印类似redefining module Balance 的警告。我当然不希望在生产中发生这种情况。

  2. AFAIK 此代码编译 module.ex。但由于 module.ex 已经编译和加载,它不希望发生这种情况。

我不需要通过文件名调用这些模块上的方法,模块名也可以。但它确实必须通过动态,例如。在命令行输入“Book”应该,在检查模块是否存在后,调用函数Book.helloWorld

谢谢。

【问题讨论】:

    标签: dynamic module load elixir


    【解决方案1】:

    嗯,这就是询问的帮助:你会在你问的那一刻自己弄清楚。 ;)

    现在只使用apply(String.to_existing_atom("Elixir.Module"), :helloWorld, [])。 (可能“Module”这个名字是不允许的,不知道)

    【讨论】:

    • 小提示:你应该尽可能使用 to_existing_atom。原子不是垃圾收集!
    • 谢谢!我不知道原子不是垃圾收集的。将编辑我的答案。
    • 更多使用 String.to_existing_atom 的理由:String.to_atom 暴露了攻击向量。详情见til.hashrocket.com/posts/…
    【解决方案2】:

    请注意,您始终需要在模块前加上“Elixir”。

    defmodule Test do
      def test(text) do
        IO.puts("#{text}")
      end
    end
    
    apply(String.to_existing_atom("Elixir.Test"), :test, ["test"])
    

    打印“测试” 并返回 {:ok}

    【讨论】:

      【解决方案3】:

      这里有一个简单的解释:

      假设你有一个这样的模块:

      defmodule MyNamespace.Printer do
        def print_hello(name) do
          IO.puts("Hello, #{name}!")
        end
      end
      

      然后你有一个包含模块名称的字符串,你可以像这样在应用程序中传递它:

      module_name = "Elixir.MyNamespace.Printer"
      

      当你收到字符串module_name时,你可以像这样实例化一个模块并调用模块上的函数:

      module = String.to_existing_atom(module_name)
      module.print_hello("John")
      

      它将打印:

      Hello, John!
      

      动态调用函数 MyNamespace.Printer.print_hello/1 的另一种方式是:

      print_hello_func = &module.print_hello/1
      print_hello_func.("Jane")
      

      它将打印:

      Hello, Jane!
      

      或者如果你想将 module_name 作为 atom 和 function_name 作为 atom,并将它们传递给某个地方执行,你可以这样写:

      a_module_name = :"Elixir.MyNamespace.Printer"
      a_function_name = :print_hello
      

      然后,只要你有 a_module_name 和 a_function_name 作为原子,你就可以这样调用:

      apply(a_module_name, a_function_name, ["Jackie"])
      

      它会打印出来

      Hello, Jackie!
      

      当然,您可以将 module_name 和 function_name 作为字符串传递,然后将它们转换为原子。或者您可以将模块名称作为原子传递,函数作为对函数的引用。

      【讨论】:

        【解决方案4】:

        还要注意,模块的名称是一个原子,因此通常不需要使用String.to_existing_atom。考虑这段代码:

        defmodule T do
          def first([]), do: nil
          def first([h|t]), do: h
        end
        

        在这种情况下,您可以简单地以这种方式进行应用:

        apply(T,:first,[[1,2,3]])
        #=> 1 
        

        或者这个例子(下面的列表是 Elixir List 模块):

        apply(List,:first,[[1,2,3]]) 
        #=> 1
        

        我的意思是,如果你知道模块的名称,就没有必要将它作为字符串传递,然后将字符串转换为现有的原子。只需使用不带引号的名称即可。

        【讨论】:

        • 是的,完全正确。但就我而言,目标是使 Module-Name 动态化。当然,如果您知道模块的名称,那也可以。
        猜你喜欢
        • 2023-03-15
        • 2012-10-25
        • 1970-01-01
        • 2010-09-05
        • 2013-01-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-23
        相关资源
        最近更新 更多