代码之家  ›  专栏  ›  技术社区  ›  Aleksei Matiushkin

为什么动态调用会导致“未定义函数”?

  •  1
  • Aleksei Matiushkin  · 技术社区  · 7 年前

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

    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
    

    这种动态呼叫分派有什么问题,为什么错误消息与实际情况相矛盾?

    1 回复  |  直到 7 年前
        1
  •  2
  •   Dogbert    7 年前

    错误信息是误导性的。 & unquote(:"A.#{name}")(&1) 将按字面上的名称调用函数 A.test 在当前范围内,而不是 test/1 模块功能 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
    

    把它叫做 测试/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