如何将函数传递给elixir中的宏?

时间:2016-05-09 04:25:18

标签: elixir ecto

我正在开发一个模块,在use d时扩展其他模块的功能。问题是,有时我想在模块定义中编写一些自定义代码,用于提升模块功能:

所以看起来像:

defmodule Included do
  defmacro __using__(_) do
    quote unquote: false do
      # a lot of code
      model
      |> unquote(@callback)
      |> another_bunch_of_things_in_pipe
    end
  end
end

我的模块

defmodule Poor do
  use Included

  @callback fn(model) -> %{ model | poor: true } end
end

毫不奇怪,我有invalid quoted expression: #Function<0.100338 in file:web/models/user.ex>。我发现Jose Valim提到不可能取消匿名功能。

那么,您能介绍一下在elixir中传递和调用自定义代码到macros的好方法吗?

2 个答案:

答案 0 :(得分:3)

quote正在Poor模块的上下文中运行,因此您无需取消引用模块属性。但是,@callback保留供内部使用,因此您需要使用其他名称:

defmodule Included do
  defmacro __using__(_) do
    quote do
      @callback_fun.()
    end
  end
end

defmodule Poor do
  @callback_fun fn -> IO.puts "hello from callback" end
  use Included
end

您也可以将该函数作为参数传递给use,我认为它更清晰,因为它直接显示了函数的使用位置:

defmodule Included do
  defmacro __using__(opts) do
    fun = Keyword.get(opts, :callback)
    quote bind_quoted: [fun: fun] do
      fun.()
    end
  end
end

defmodule Poor do
  use Included, callback: fn ->
    IO.puts "hello from callback"
  end
end

答案 1 :(得分:1)

您的代码存在一些问题(除了遗漏的endfn,这可能是一个错字):

  • @callback是Elixir中的一个特殊属性。您可以阅读更多相关信息here。您必须使用其他名称。

  • 为模块属性指定内容的语法为@attr value,而不是@attr = value

  • 您不需要unquote该属性,您可以直接进入@attr.()

  • 当您致电__using__时调用use,您需要在调用use之前声明该属性。

这是固定版本:

defmodule Included do
  defmacro __using__(_) do
    quote do
      model = %{poor: false}
      model
      |> @back.()
      |> IO.inspect
    end
  end
end

defmodule Poor do
  @back fn(model) -> %{ model | poor: true } end
  use Included
end

输出:

%{poor: true}