在 Elixir 中使用类型规范中的原子列表
Use a list of atoms in type spec in Elixir
假设我有以下内容:
@some_list [:a, :b, :c, :d]
@type some_type :: :a | :b | :c | :d
是否有一些方法可以使用 @some_list
来定义 @type some_type
而无需明确使用 @some_list
中包含的相同原子?
编辑:为了清楚起见,我想将 @some_list
的内容重新用于 @type
构造,而 @some_list
仍可用于其他用途。
这在某种程度上可以通过一些元编程实现。
类型是特殊的,所以不能只在那里传递任意值并期望它在编译阶段被理解或扩展。
幸运的是,可以使用外部宏来定义模块和类型属性。
请注意下面的代码不执行任何完整性检查,也不支持空列表和一个元素的列表
# 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
现在我们可以从这个模块外部使用
require Helpers
Helpers.one_of(some_list, ~w|a b c d|a)
上面的调用类似于
@some_list ~w|a b c d|a
@type some_list :: :a | :b | :c | :d
我想到了以下内容,它几乎是在编译时从列表中逐字构造字符串 :a | :b | :c
并将其注入类型:
@some_list [:a, :b, :c]
@type t ::
unquote(
@some_list
|> Enum.map(&inspect/1)
|> Enum.join(" | ")
|> Code.string_to_quoted!()
)
它可能比 Aleksei Matiushkin 的解决方案更 hacky,但代码更少,如果是一次性的,可能更容易理解。
假设我有以下内容:
@some_list [:a, :b, :c, :d]
@type some_type :: :a | :b | :c | :d
是否有一些方法可以使用 @some_list
来定义 @type some_type
而无需明确使用 @some_list
中包含的相同原子?
编辑:为了清楚起见,我想将 @some_list
的内容重新用于 @type
构造,而 @some_list
仍可用于其他用途。
这在某种程度上可以通过一些元编程实现。
类型是特殊的,所以不能只在那里传递任意值并期望它在编译阶段被理解或扩展。
幸运的是,可以使用外部宏来定义模块和类型属性。
请注意下面的代码不执行任何完整性检查,也不支持空列表和一个元素的列表
# 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
现在我们可以从这个模块外部使用
require Helpers
Helpers.one_of(some_list, ~w|a b c d|a)
上面的调用类似于
@some_list ~w|a b c d|a
@type some_list :: :a | :b | :c | :d
我想到了以下内容,它几乎是在编译时从列表中逐字构造字符串 :a | :b | :c
并将其注入类型:
@some_list [:a, :b, :c]
@type t ::
unquote(
@some_list
|> Enum.map(&inspect/1)
|> Enum.join(" | ")
|> Code.string_to_quoted!()
)
它可能比 Aleksei Matiushkin 的解决方案更 hacky,但代码更少,如果是一次性的,可能更容易理解。