Elixir - 如何在宏中取消引用一系列函数?

时间:2016-05-10 10:19:10

标签: metaprogramming elixir

免责声明:我知道可以用更简单的方式编写代码,但您应该理解我将简化代码发布到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模块定义一个函数,该函数为上面列表中的每个项目取一个参数并返回一个值。所以,我这样做:

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. 为什么第二种方法没有定义函数?

1 个答案:

答案 0 :(得分:1)

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

[[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