The value of the macro here would be:
- it creates an abstraction that hides the implementation detail (using tuples is a detail)
- it makes the code easier to read and write, by not forcing the user to manually duplicate code everywhere
To further expand, invoking deftype Name, String.t()
would generate:
defmodule Name do
@opaque t :: {:name, String.t()}
@spec new(val :: String.t()) :: t
def new(val) when is_binary(val), do: {:name, val}
@spec extract(name :: t) :: String.t()
def extract({:name, val}), do: val
@spec name?(data :: any) :: boolean()
def name?({:name, val}) when is_binary(val), do: true
def name?(_data), do: false
@spec is_name(data :: any) ::
{:__block__ | {:., [], [:andalso | :erlang, ...]}, [],
[{:= | {any, any, any}, [], [...]}, ...]}
defguard is_name(value)
when is_tuple(value) and elem(value, 0) == :name and is_binary(elem(value, 1))
end
While I could in theory define this set of functions for every tuple, I find it that a macro would help with that, while cutting on manual duplication.