簡體   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