This is possible to some extent with a bit of metaprogramming.
Types are special, so one cannot just pass the arbitrary value there and expect it to be understood or expanded during the compilation stage.
Luckily enough, one might use an external macro to define both module and type attributes.
Please note the code below does not perform any sanity check neither does support empty lists and lists of one element
# 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
Now we can use from outside of this module
require Helpers
Helpers.one_of(some_list, ~w|a b c d|a)
The call above is similar to
@some_list ~w|a b c d|a
@type some_list :: :a | :b | :c | :d