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

问题描述 投票:1回答:1

我正在学习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个回答
0
投票

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

这是因为仅在Ecto.Query.API.field/2宏内部有意义。

为什么Ecto.Query.from/2不能单独实现?因为它需要访问列的[[value,由于Ecto.Query.from/2,它在宏内部不可用。


好消息是,我们仍然可能会欺骗Elixir编译器,从而绕过宏卫生:)不要在家中或学校中这样做。

field/2

不幸的是,该技巧不适用于macro hygiene,因为defmodule M do
  defmacro magic({:^, _, [arg]}) do
    quote do: var!(unquote(arg))
  end
end
require M
column = :id
M.magic(^column)
#⇒ :id
会导致尝试在r.id上调用unquote(arg1).var!(unquote(arg2))方法。

为了更好地理解上述内容,您可能应该自己弄清楚哪里有可用的AST。在var!/1之外,不能调用arg1绕过卫生,而在(要注射的AST中)限制在岩石上方。这就是quote do接收整个AST转换为查询

and

处理所有上下文可用的整体的原因。
我强烈推荐Chris McCord的var!/1
© www.soinside.com 2019 - 2024. All rights reserved.