[英]How to expand multiple macros in Elixir?
I'm starting my adventure with Elixir and I need a little bit o help. 我从Elixir开始冒险,需要一点帮助。
I'm trying to simplify my structs definition and validation by using macros. 我正在尝试通过使用宏简化结构的定义和验证。 The goal is to automatically inject
defstruct
and Vex library validators based on provided options in modules using it. 目标是根据使用
defstruct
和Vex库验证程序的模块中提供的选项自动注入它。
I've come up with the code as follows: 我提出了如下代码:
defmodule PdfGenerator.BibTypes.TypeDefinition do
@callback valid?(%{}) :: boolean
defmacro __using__(mod: mod, style: style, required: required, optional: optional) do
required_props = required |> Enum.map(&{:"#{&1}", nil})
optional_props = optional |> Enum.map(&{:"#{&1}", nil})
quote location: :keep do
defstruct unquote([{:style, style}] ++ required_props ++ optional_props)
@behaviour PdfGenerator.BibTypes.TypeDefinition
use Vex.Struct
def cast(%{} = map) do
styled_map = Map.put(map, :style, unquote(style))
struct_from_map(styled_map, as: %unquote(mod){})
end
defp struct_from_map(a_map, as: a_struct) do
keys =
Map.keys(a_struct)
|> Enum.filter(fn x -> x != :__struct__ end)
processed_map =
for key <- keys, into: %{} do
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
{key, value}
end
a_struct = Map.merge(a_struct, processed_map)
a_struct
end
validates(
:style,
presence: true,
inclusion: [unquote(style)]
)
end
Enum.each(required, fn prop ->
quote location: :keep do
validates(
unquote(prop),
presence: true
)
end
end)
end
end
And I'm using this macro in another module: 我在另一个模块中使用此宏:
defmodule PdfGenerator.BibTypes.Booklet do
use PdfGenerator.BibTypes.TypeDefinition,
mod: __MODULE__,
style: "booklet",
required: [:title],
optional: [:author, :howpublished, :address, :month, :year, :note]
end
I want PdfGenerator.BibTypes.Booklet
module, after macro expansion, to look as follows: 我希望宏扩展后的
PdfGenerator.BibTypes.Booklet
模块如下所示:
defmodule PdfGenerator.BibTypes.Booklet do
defstruct style: "booklet",
title: nil,
author: nil,
howpublished: nil,
address: nil,
month: nil,
year: nil,
note: nil
@behaviour PdfGenerator.BibTypes.TypeDefinition
use Vex.Struct
def cast(%{} = map) do
styled_map = Map.put(map, :style, "booklet")
struct_from_map(styled_map, as: %PdfGenerator.BibTypes.Booklet{})
end
defp struct_from_map(a_map, as: a_struct) do
keys =
Map.keys(a_struct)
|> Enum.filter(fn x -> x != :__struct__ end)
processed_map =
for key <- keys, into: %{} do
value = Map.get(a_map, key) || Map.get(a_map, to_string(key))
{key, value}
end
a_struct = Map.merge(a_struct, processed_map)
a_struct
end
validates(
:style,
presence: true,
inclusion: ["booklet"]
)
validates(
:title,
presence: true
)
end
As you can see, based on required
option, I'm trying to expand to Vex
-specific macro (which in turn should be expanded further on in Vex.Struct
macro definition) validates(:<PROP_NAME>, presence: true)
for every value in required
list. 如您所见,基于
required
选项,我试图扩展到Vex
特定的宏(依次应在Vex.Struct
宏定义中进一步扩展) validates(:<PROP_NAME>, presence: true)
required
列表中的值。 This macro code works (but without these validators for required values) when I remove last block from __using__
macro: 当我从
__using__
宏中删除最后一个块时,此宏代码有效(但没有用于所需值的这些验证器):
Enum.each(required, fn prop ->
quote location: :keep do
validates(
unquote(prop),
presence: true
)
end
end)
But with it, when I'm trying to issue following command in the iex
console: %PdfGenerator.BibTypes.Booklet{}
但是有了它,当我尝试在
iex
控制台中发出以下命令时: %PdfGenerator.BibTypes.Booklet{}
I get: 我得到:
** (CompileError) iex:1: PdfGenerator.BibTypes.Booklet.__struct__/1 is undefined, cannot expand struct PdfGenerator.BibTypes.Booklet
Any idea, what am I doing wrong? 任何想法,我在做什么错? Any hint would be greatly appreciated as I'm pretty new to the whole Elixir and macros world.
任何提示将不胜感激,因为我对整个Elixir和宏世界还很陌生。
Since you did not provide the MCVE , it's extremely hard to test the solution, but at the first glance the issue is you expect some magic from Kernel.SpecialForms.quote/2
, while it does not implicitly inject anything anywhere , it just produces an AST . 由于您未提供MCVE ,因此很难测试解决方案,但是乍一看,问题在于您希望
Kernel.SpecialForms.quote/2
一些魔力,尽管它不会在任何地方隐式注入任何内容 ,但只会产生一个AST 。
When you call 你打电话时
Enum.each(...)
as the last line of quote do
block, the result of this call is returned as AST from quote do
. 作为
quote do
块的最后一行,此调用的结果从quote do
作为AST返回 。 That said, the current __using__
implementation injects the result of the call to quote do: :ok
, which is apparently :ok
. 也就是说,当前的
__using__
实现会注入对quote do: :ok
的调用结果quote do: :ok
,显然是:ok
。 What you need, is to build the list of clauses to be injected: 您需要的是构建要注入的子句的列表:
defmacro __using__(mod: mod, ...) do
# preparation
ast_defstruct =
quote location: :keep do
# whole stuff for defstruct
end
# NB! last term will be returned from `__using__`!
[
ast_defstruct |
Enum.map(required, fn prop ->
quote location: :keep,
do: validates(unquote(prop), presence: true)
end)
]
By using Enum.map/2
we collect quoted ASTs for each element, and append them to already built AST for defstruct
creation. 通过使用
Enum.map/2
我们为每个元素收集带引号的AST, 并将它们附加到已经构建的AST中以进行defstruct
创建。 We return a list (which is a proper AST,) containing many clauses. 我们返回一个包含许多子句的列表(这是一个适当的AST)。
Still, I am unsure if this is the only glitch due to lack of MCVE, but this is definitely the proper fix to start with. 不过,我不确定这是否是由于缺少MCVE而造成的唯一故障,但这绝对是开始的正确方法。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.