首页 > 解决方案 > 为什么动态调用会导致“未定义函数”?

问题描述

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

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

这种动态呼叫调度有什么问题,为什么错误消息与现实相矛盾?

标签: macroselixirmetaprogramming

解决方案


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

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

要使其调用test/1模块的功能A,您可以执行以下操作& 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

推荐阅读