【问题标题】:How do you pass a function capture to a macro in Elixir?如何将函数捕获传递给 Elixir 中的宏?
【发布时间】:2020-07-17 14:44:09
【问题描述】:

这里不行。使用Macro.expand/2 也无济于事。

    defmodule ExBs.Component do
      defmacro defcomp(name, opts) do
        func = Keyword.get(opts, :func)
        IO.inspect(func)
        func.("foo")
      end
    end

    defmodule ExBs.Foo do
      import ExBs.Component

      defcomp(:foo,
        func: &Phoenix.HTML.Link.link/2
      )
    end

这是 IEx 输出:

    [{:__aliases__, [line: 24], [:Phoenix, :HTML, :Link]}, :link]

    ** (BadFunctionError) expected a function, got: nil
        expanding macro: ExBs.Component.defcomp/2

有可能吗?

【问题讨论】:

    标签: macros elixir


    【解决方案1】:

    宏执行接收 AST 并返回 AST。返回的 AST 被注入到调用这个宏的地方,而不是调用宏。要返回 AST,请使用 Kernel.quote/2。也就是说,以下内容将起作用:

    defmodule ExBs.Component do
      defmacro defcomp(name, opts) do
        quote do
          func = Keyword.get(unquote(opts), :func, fn _ -> "" end)
          IO.inspect(func)
          func.("foo")
        end
      end
    end
    
    defmodule ExBs.Foo do
      import ExBs.Component
    
      defcomp(:foo,
        func: &IO.inspect/1
      )
    end
    
    #⇒ #Function<0.48519526 in file:iex>
    "foo"
    

    请注意,该函数是在编译阶段执行的,即在宏展开时。也就是说,生成的 BEAM 没有这个函数的踪迹。

    我怀疑这是否是您真正想要的,但在不知道您要达到的目标的情况下,不可能提出更有价值的建议。无论如何,现在代码复制并按设计工作。


    猜想:如果你想声明函数:foo包装func,下面会做:

    defmodule ExBs.Component do
      defmacro defcomp(name, opts) do
        func = Keyword.get(opts, :func)
        quote do
          def unquote(name)(), do: unquote(func).("foo")
        end
      end
    end
    
    defmodule ExBs.Foo do
      import ExBs.Component
    
      defcomp(:foo,
        func: &IO.inspect/1
      )
    end
    
    ExBs.Foo.foo
    #⇒ "foo"
    

    【讨论】:

      猜你喜欢
      • 2016-09-03
      • 2018-02-05
      • 1970-01-01
      • 2018-05-16
      • 2014-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多