如何编写类似于 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。
我在学习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。