【问题标题】:How to write Elixir macro similar to Ecto's field/2?如何编写类似于 Ecto 的 field/2 的 Elixir 宏?
【发布时间】:2020-03-11 20:46:18
【问题描述】:

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

【问题讨论】:

标签: macros elixir abstract-syntax-tree ecto


【解决方案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

如果没有像 inplace eval 这样的绝对讨厌的技巧,我无法让 quote do: unquote(a1).unquote(a2) 工作。

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


我强烈推荐 Chris McCord 的 Metaprogramming Elixir

【讨论】:

  • 实际上我正在寻找的是完全按照field 所做的方法,因此在id 存储为原子时将条件“where r.id = 7”插入到where 子句中在一些变量中。当然在from之外使用它是没有意义的。
  • field 不做任何事情,正如我所说,from/2 在 AST 中将其作为普通原子处理。
  • 好的,我想我现在明白了。所以基本上from/2 正在寻找:field 并处理它,对吧?
  • 是的,fragment/2 也是如此。 Ecto.Query.API.field/2就在那里,所以他们可以附加文档
猜你喜欢
  • 1970-01-01
  • 2010-12-08
  • 1970-01-01
  • 2014-02-12
  • 1970-01-01
  • 2010-12-09
  • 2016-08-27
  • 1970-01-01
  • 2016-06-02
相关资源
最近更新 更多