为什么动态调用会导致“undefined function”?

Why the dynamic call results in “undefined function”?

我有一个模块可以通过以下方式将外部调用动态路由到它自己的函数:

defmodule A do
  defmacro call(name) do
    quote do
      fun = & unquote(:"A.#{name}")(&1)
      fun.(:foo)
    end
  end

  def test(param), do: IO.inspect(param, label: "test")
end
#⇒ {:module, A, ..., {:test, 1}}

模块编译成功,A.test/1存在。

A.test :foo
#⇒ test: :foo

现在我尝试将其命名为:

defmodule B do
  require A
  def test, do: A.call(:test)
end
#⇒ ** (CompileError) iex:21: undefined function A.test/1
#      (stdlib) lists.erl:1338: :lists.foreach/2
#      (stdlib) erl_eval.erl:677: :erl_eval.do_apply/6
#      (iex) lib/iex/evaluator.ex:249: IEx.Evaluator.handle_eval/5

这个动态调用调度有什么问题,为什么错误消息与实际情况相矛盾?

错误消息具有误导性。 & unquote(:"A.#{name}")(&1) 将在当前范围内调用字面上名为 A.test 的函数,而不是模块 A:

test/1 函数
defmodule A do
  defmacro call(name) do
    quote do
      fun = & unquote(:"A.#{name}")(&1)
      fun.(:foo)
    end
  end

  def unquote(:"A.test")(param), do: IO.inspect(param, label: "!!!")
end

defmodule B do
  require A
  import A
  def test, do: A.call(:test)
end

B.test

输出:

!!!: :foo

要让它调用模块Atest/1函数,你可以& A.unquote(:"#{name}")(&1):

defmodule A do
  defmacro call(name) do
    quote do
      fun = & A.unquote(:"#{name}")(&1)
      fun.(:foo)
    end
  end

  def test(param), do: IO.inspect(param, label: "test")
end

defmodule B do
  require A
  def test, do: A.call(:test)
end

B.test

输出:

test: :foo