如何编写类似于Ecto的field / 2的Elixir宏?

时间:2019-11-15 16:48:30

标签: macros elixir abstract-syntax-tree ecto

我正在学习Elixir,遇到了这种情况:
我有一个Ecto模式,我想做一个像“ get_by”这样的函数,它带有一个列名,并且它的值是这样的一个参数:get_by(:id, 7) 因此,该功能的工作版本如下:

def get_by(column, value) do
  Repo.all(
    from(
      r in __MODULE__,
      where: field(r, ^column) == ^value,
    )
  )
end

我知道它可以完全正常工作,但是我想知道field宏的工作原理。
原始代码对我来说太难读了。我试图在宏中使用AST,但似乎没有任何效果。我最好的是:

defmacro magic(var, {:^, _, [{column, _, _}]}) do
  dot = {:., [], [var, column]}
  {dot, [], []}
end

但这将返回r.column而不是绑定到column变量的原子。
应当如何编写宏以返回r.id

1 个答案:

答案 0 :(得分:1)

如果您查看Ecto.Query.API.field/2的源代码,则会看到对该函数的显式调用(不是宏),会引发

这是因为只有在Ecto.Query.from/2宏中才有意义。

您想要的东西在某种程度上仍然可以实现;不是带点符号(AFAICT,而是带有Access

defmodule M do
  defmacro magic(a1, {:^, _, [a2]}) do
    quote do: unquote(a1)[unquote(a2)]
  end
end
import M
{r, column} = {%{id: 42}, :id}
magic(r, ^column)
#⇒ 42

如果没有就地评估等绝对讨厌的技巧,我将无法quote do: unquote(a1).unquote(a2)工作。

为了更好地理解宏,您可能应该自己弄清楚哪里可以使用AST。


我强烈推荐Chris McCord的Metaprogramming Elixir