在Elixir中调用apply(String.t .....)

时间:2017-11-10 04:33:12

标签: elixir

如何将模块名称作为字符串调用模块的方法?我知道怎么用申请。我怎样才能将模块的名称用作字符串?

2 个答案:

答案 0 :(得分:2)

您必须将字符串转换为原子。所有Elixir模块都位于Elixir命名空间中(基本上是为了防止名称冲突),因此表示Elixir模块的原子都以"Elixir."开头:

iex(1)> defmodule M, do: def foo, do: IO.puts "BAR"
iex(2)> name = "M"
iex(3)> apply(String.to_atom("Elixir." <> name), :foo, [])
BAR

答案 1 :(得分:0)

Elixir 模块在使用和打印时省略其名称中的 Elixir. 前导部分。当 Elixir 和 Erlang 模块都被编译时,它们会生成一个 Beam 文件。需要这个前缀是为了避免 Elixir 和 Erlang 同名的名称冲突。

当您编写和阅读代码时,您不必考虑这一点,因为它是自动处理的。注意左右两边都是原子。需要引号来处理特殊符号,模块只是以大写字母开头的原子,作为语法糖,在这种情况下可以省略 :

iex(1)> :"Elixir.SomeModule" == SomeModule
true
iex(2)> :"Elixir.SomeModule" == Elixir.SomeModule
true

由于编译器会自动为您处理这些前缀,因此建议您在将字符串转换为模块名称时也让编译器处理这些前缀。

这可以通过使用 Module.concat/1Module.safe_concat/1 来完成。它们之间的区别在于,第一个使用 binary_to_atom/2,第二个使用 binary_to_existing_atom/2 将字符串转换为原子。原子不会被垃圾收集,因此将随机用户输入作为参数提供给 binary_to_atom/2 或其等效的 Elixir String.to_atom/1 是危险的,因为这可能导致 VM 崩溃。在此特定用例中,如果您尝试从不存在的模块调用函数,则会遇到错误。

知道这一点,Module.safe_concat/1 函数是最合适的,因为:

  • 它让编译器处理 Elixir. 前缀。
  • 它不会因为达到节点上允许的原子数限制而使 VM 崩溃。
  • 如果您提供的字符串与现有模块不对应,则会产生错误。

成功案例:

iex(1)> Module.safe_concat(["String"]).upcase("xyz")        
"XYZ"

模块名称有拼写错误时报错:

iex(2)> Module.safe_concat(["Strign"]).upcase("xyz")
** (ArgumentError) argument error
    :erlang.binary_to_existing_atom("Elixir.Strign", :utf8)
    (elixir 1.12.0-dev) src/elixir_aliases.erl:126: :elixir_aliases.safe_concat/1