Macros 如何在Elixir中扩展多个宏?

Macros 如何在Elixir中扩展多个宏?,macros,elixir,Macros,Elixir,我要用长生不老药开始我的冒险,我需要一点帮助 我试图通过使用宏来简化我的结构定义和验证。目标是根据使用它的模块中提供的选项,自动注入defstruct和Vex库验证器 我提出了如下代码: defmodule PdfGenerator.BibTypes.TypeDefinition do @callback valid?(%{}) :: boolean defmacro __using__(mod: mod, style: style, required: required, optio

我要用长生不老药开始我的冒险,我需要一点帮助

我试图通过使用宏来简化我的结构定义和验证。目标是根据使用它的模块中提供的选项,自动注入
defstruct
和Vex库验证器

我提出了如下代码:

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
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
我希望在宏展开后,PdfGenerator.BibTypes.bulkbook模块的外观如下所示:

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
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
但是有了它,当我试图在
iex
控制台中发出以下命令时:
%PdfGenerator.BibTypes.Booklet{}

我得到:

** (CompileError) iex:1: PdfGenerator.BibTypes.Booklet.__struct__/1 is undefined, cannot expand struct PdfGenerator.BibTypes.Booklet

你知道我做错了什么吗?任何提示都将不胜感激,因为我对整个长生不老药和宏世界都很陌生

由于您没有提供,因此测试解决方案非常困难,但乍一看,问题在于您希望从中获得一些魔力,虽然它不会隐式地在任何地方注入任何内容,但它只会生成一个AST

当你打电话的时候

Enum.each(…)
作为
quote do
块的最后一行,此调用的结果作为AST从
quote do
返回。也就是说,当前的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu。您需要的是构建要注入的子句列表:

defmacro\uuuu使用(mod:mod,…)do
#准备工作
ast_defstruct=
报价位置::保留do
#defstruct的全部内容
结束
#注意!最后一个学期将使用返回!
[
ast_defstruct|
枚举映射(必需,fn属性->
报价位置::保留,
do:验证(取消引用(prop),状态:true)
(完)
]
通过使用,我们为每个元素收集引用的AST,并将它们附加到已构建的AST中,以便
defstruct
创建。我们返回一个包含许多子句的列表(这是一个适当的AST)


尽管如此,我不确定这是否是由于缺乏MCVE而导致的唯一故障,但这绝对是一个正确的解决方案。

是的,就是这样。我忘记了一个非常简单的事实:Elixir中的每个函数都会返回一些东西。非常感谢你的帮助和投入的时间:)没有MCVE——被否决了。