繁体   English   中英

如何在Elixir中扩展多个宏?

[英]How to expand multiple macros in Elixir?

我从Elixir开始冒险,需要一点帮助。

我正在尝试通过使用宏简化结构的定义和验证。 目标是根据使用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
  use PdfGenerator.BibTypes.TypeDefinition,
    mod: __MODULE__,
    style: "booklet",
    required: [:title],
    optional: [:author, :howpublished, :address, :month, :year, :note]
end

我希望宏扩展后的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

如您所见,基于required选项,我试图扩展到Vex特定的宏(依次应在Vex.Struct宏定义中进一步扩展) validates(:<PROP_NAME>, presence: true) required列表中的值。 当我从__using__宏中删除最后一个块时,此宏代码有效(但没有用于所需值的这些验证器):

Enum.each(required, fn prop ->
  quote location: :keep do
    validates(
      unquote(prop),
      presence: true
    )
  end
end)

但是有了它,当我尝试在iex控制台中发出以下命令时: %PdfGenerator.BibTypes.Booklet{}

我得到:

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

任何想法,我在做什么错? 任何提示将不胜感激,因为我对整个Elixir和宏世界还很陌生。

由于您未提供MCVE ,因此很难测试解决方案,但是乍一看,问题在于您希望Kernel.SpecialForms.quote/2一些魔力,尽管它不会在任何地方隐式注入任何内容 ,但只会产生一个AST

你打电话时

Enum.each(...)

作为quote do块的最后一行,此调用的结果从quote do 作为AST返回 也就是说,当前的__using__实现会注入对quote do: :ok的调用结果quote do: :ok ,显然是:ok 您需要的是构建要注入的子句的列表:

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)
  ]

通过使用Enum.map/2我们为每个元素收集带引号的AST, 并将它们附加到已经构建的AST中以进行defstruct创建。 我们返回一个包含许多子句的列表(这是一个适当的AST)。

不过,我不确定这是否是由于缺少MCVE而造成的唯一故障,但这绝对是开始的正确方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM