在 Elixir 中使用类型规范中的原子列表

nor*_*ok2 2 types list elixir

假设我有以下内容:

@some_list [:a, :b, :c, :d]

@type some_type :: :a | :b | :c | :d
Run Code Online (Sandbox Code Playgroud)

有没有某种方法可以用来@some_list定义@type some_type而不显式使用 中包含的相同原子@some_list


编辑:为了明确起见,我想重用构造的内容@some_list@type同时@some_list仍可用于其他用途。

Paw*_*rok 7

:a | :b | :c我想出了以下内容,它几乎在编译时从列表中构造字符串并将其注入到类型中:

@some_list [:a, :b, :c]
@type t ::
        unquote(
          @some_list
          |> Enum.map(&inspect/1)
          |> Enum.join(" | ")
          |> Code.string_to_quoted!()
        )
Run Code Online (Sandbox Code Playgroud)

它可能比 Aleksei Matiushkin 的解决方案更hacky,但代码更少,如果是一次性的,可能更容易理解。


Ale*_*kin 5

通过一些元编程,这在某种程度上是可能的。

类型是特殊的,因此不能只是将任意值传递到那里并期望它在编译阶段被理解或扩展。

幸运的是,人们可以使用外部宏来定义模块类型属性。

请注意,下面的代码不执行任何健全性检查,也不支持空列表和一个元素的列表

# inside module Helpers
defmacro one_of(name, list) do
  # attribute name must be an atom, type name we pass as is,
  #   hence we need to extract the atom name
  {attr_name, _, _} = name

  # reverse a list to iterate from the tail
  #   check what `quote do: :a | :b | :c` returns
  [last, prev | rest] =
    list
    |> Macro.expand(__CALLER__)
    |> Enum.reverse()

  # here we build the raw AST, 
  #   otherwise the compilation would fail
  type =
    Enum.reduce(rest, {:|, [], [prev, last]}, &{:|, [], [&1, &2]})

  quote do
    # declare the attribute
    Module.put_attribute(__MODULE__, unquote(attr_name), unquote(list))
    # declare the type
    @type unquote(name) :: unquote(type)
  end
end
Run Code Online (Sandbox Code Playgroud)

现在我们可以从该模块外部使用

require Helpers

Helpers.one_of(some_list, ~w|a b c d|a)
Run Code Online (Sandbox Code Playgroud)

上面的调用类似于

@some_list ~w|a b c d|a
@type some_list :: :a | :b | :c | :d
Run Code Online (Sandbox Code Playgroud)