在宏中生成长生不老药结构的正确方法

Right way to produce elixir struct in macro

我有以下设计模式:我有一个长生不老药模块,响应 growing/changing 数量的 functions/0,称为 Defaults .我还有 CustomConfig 模块,它基本上是结构,派生默认值并假设像这样实例化:

%CustomConfig{ foo: "bar" }

其中初始化的属性被覆盖,其他属性取自 Defaults,而那些在 Defaults 中没有同名函数的属性将被拒绝。到目前为止,还不错。

为了独立于内容(Defaults 中的函数列表)实现此行为,我使用了一个宏(在其他模块中,因为不能使用在同一模块中定义的结构声明中的宏):

defmacro define_struct_with_defaults do
  quote do
    defstruct Map.to_list(
      quote do: unquote(Enum.reduce(Dict.keys(
                        Defaults.__info__(:functions)), %{}, fn(k, acc) ->
        Map.put(acc, :"#{k}", apply(Defaults, :"#{k}", []))
      end)))
  end
end

虽然这工作正常,但我很确定,应该有更多 straightforward/elegant/less-cumbersome 方法来实现此功能。

所以我的问题是:如何在不围绕 map-reduce 跳舞的情况下从 Map 声明 defstruct

您的代码中有很多实际上不需要的间接访问。示例:

  1. :"#{k}" 可能只是 k 因为 k 已经是一个原子
  2. 您不需要 Dict.keys/1 因为您可以在循环内的键上进行模式匹配
  3. 您不需要地图,因为您可以 return 直接从 Enum.map(或从理解)
  4. 中获取列表
  5. 您不需要宏,因为您可以将任何表达式传递给 defstruct

重写代码的方法如下:

defmodule Default do
  def foo, do: 1
  def bar, do: 2
end

defmodule Config do
  data =
    # Get all functions with 0 arity and the respective default
    for {k, 0} <- Default.__info__(:functions) do
      {k, apply(Default, k, [])}
    end

  defstruct data
end

Elixir 的好处之一是您可以 write assertive code。如果你利用它,你将对你的代码越来越有信心。