Elixir - 如何在我的宏中取消对函数数组的引用?

Elixir - How can I unquote an array of functions in my macro?

免责声明:我知道可以用更简单的方式编写代码,但您应该明白我post 将代码简化为 SO。

我有一个模块 Simple,它使用 Included:

defmodule Simple do
  use Included,
    events: [
      [ 
        name: :start,
        callback: fn(x) -> x * 2 end
      ], [
        name: :finish,,
        callback: fn(x) -> x * 3 ; x end
      ]
    ]
end

我希望 Included 模块定义一个函数,它为上面列表中的每一项接受一个参数,return 一个值。所以,我这样做:

defmodule Included do
  defmacro __using__(opts)
    events = Keyword.get(opts, :events)

    quote bind_quoted: [events: events] do
      events
      |> Enum.each(fn(event) ->
        def unquote(event[:name])(x) do
          x
          |> unquote(event[:callback]).()
          |> IO.puts
      end)
    end    
  end
end

这里的问题是我收到 invalid quoted expression: #Function<0.105634730。我尝试以另一种方式实现它:

defmodule Included do
  defmacro __using__(opts)
    events = Keyword.get(opts, :events)

    events
    |> Enum.each(fn(event) ->
      quote bind_quoted: [event: event] do
        def unquote(event[:name])(x) do
          x
          |> event[:callback].()
          |> IO.puts
         end
      end
    end)
  end
end

但在这种情况下,我还没有看到定义的函数。 (没有错误,这里没有函数Simple.start/1Simple.finish/1)。

我的问题是:

  1. 如何定义所需的功能?
  2. 为什么第二种方法中没有定义函数?

我不是 100% 确定为什么,但是在 Included.__using__/1 中的 quote 中,函数的 AST 正在转换为实际函数。如果您在 quote 的开头添加 IO.inspect(events),您将得到:

[[name: :start, callback: #Function<0.18558591 in file:c.exs>],
 [name: :finish, callback: #Function<1.18558591 in file:c.exs>]]

我找到的解决方法是在事件中转义 :callback

defmacro __using__(opts) do
  events = for event <- opts[:events] do
    Keyword.update!(event, :callback, &Macro.escape/1)
  end
  quote bind_quoted: [events: events] do
  ...
end

最终代码:

defmodule Included do
  defmacro __using__(opts) do
    events = for event <- opts[:events] do
      Keyword.update!(event, :callback, &Macro.escape/1)
    end
    quote bind_quoted: [events: events] do
      events
      |> Enum.each(fn(event) ->
        def unquote(event[:name])(x) do
          x
          |> unquote(event[:callback]).()
          |> IO.puts
        end
      end)
    end    
  end
end

defmodule Simple do
  use Included,
    events: [
      [ 
        name: :start,
        callback: fn(x) -> x * 2 end
      ], [
        name: :finish,
        callback: fn(x) -> x * 3 ; x end
      ]
    ]
end

Simple.start 10
Simple.finish 10

输出:

20
10