Defining types for struct fields

You can always reduce the opts into your struct however you want. You can validate, rename, transform, do whatever.

A simple example where opts of the wrong type are simply rejected

  def new(opts \\ []) do
    Enum.reduce(opts, %__MODULE__{}, &put_opt/2)
  end

  defp put_opt(opt, acc)

  defp put_opt({:a, a}, acc) when is_integer(a) do
    %{acc | a: a}
  end

  defp put_opt({:b, b}, acc) when is_binary(b) do
    %{acc | b: b}
  end

  defp put_opt({:c, c}, acc) when is_atom(c) do
    %{acc | c: c}
  end

  defp put_opt(_opt, acc) do
    acc
  end