Build a union type from a list?

Is it possible to build a union type from a list? For example, something like:

defmodule HatEnum do
  @variants [:bowler, :sombrero]

  # duplicated knowledge.
  # declare this based on @variants instead?
  @type t :: :bowler | :sombrero

  @spec name(t()) :: String.t()
  def name(variant) when variant in @variants do
    variant
    |> to_string()
    |> String.capitalize()
  end
end
2 Likes

It seems that no. You can check this comment.

1 Like

@josevalim Is it possible to do this with a macro? If not, might this be worth supporting in the language?

  @variants [:bowler, :sombrero]
  @type t :: union_type(@variants)
1 Like

It’s possible. It’s not pretty. It would look something like this:

requireT
T.make_union_type(:t, @variants)

(In another module T)

defmacro make_union_type(t, a2u) do

  {_, atom_list@} = Code.eval_quoted(a2u, __CALLER__)

  {:@, [], [:type, {:::, [], [t, unroll_atoms(atoms_list)]}]}

end

Unroll atoms is left as an exercise for the user but it takes a list of atoms and turns it into nested {:|, [], [•••, •••]} thruples.

2 Likes

I use the following approach:

  # Converts a list of atoms into a typespec
  type = &Enum.reduce(&1, fn x, acc -> {:|, [], [x, acc]} end)

  @grammatical_gender [
    :animate,
    :inanimate,
    :personal,
    :common,
    :feminine,
    :masculine,
    :neuter
  ]
  @type grammatical_gender :: unquote(type.(@grammatical_gender))
11 Likes

Oh, damn the unquote trick!! Beautiful