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

How to write Elixir macro similar to Ecto's field/2?

我在学习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

但是这个 returns r.column 而不是绑定到 column 变量的原子。
宏应该怎么写成returnr.id?

如果您查看 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