It’s a matter of taste maybe. In Haskell you have the annotations on top of the functions and I like it.
Elixir supports multiple clauses, so from that function:
def hostname(%URI{host: host}), do: host
def hostname(url) when is_binary(url), do: hostname(URI.parse(url))
def hostname(nil), do: nil
You could get something like this:
def hostname(%URI{host: host} :: URI.t()) :: String.t() | nil, do: host
def hostname(url :: binary()) when is_binary(url) :: String.t() | nil, do: hostname(URI.parse(url))
def hostname(nil :: nil) :: nil, do: nil
And I think it’s pretty unreadable.
Or maybe the type system will not collect all clauses (I’m sure it will, but it may not collect all clauses declarations), so you may have to do something like this:
def hostname(%URI{host: host} :: URI.t() | String.t() | nil) :: String.t() | nil, do: host
def hostname(url :: URI.t() | String.t() | nil) when is_binary(url) :: String.t() | nil, do: hostname(URI.parse(url))
def hostname(nil :: URI.t() | String.t() | nil) :: String.t() | nil, do: nil
Let’s simplify a bit:
$ has_hostname :: URI.t() | String.t() | nil
$ maybe_hostname :: String.t() | nil
def hostname(%URI{host: host} :: has_hostname) :: maybe_hostname, do: host
def hostname(url :: has_hostname) when is_binary(url) :: maybe_hostname, do: hostname(URI.parse(url))
def hostname(nil :: has_hostname) :: maybe_hostname, do: nil
But really my favorite is to separate the thing with a function head:
def hostname(uri :: URI.t() | String.t() | nil) :: String.t() | nil
def hostname(%URI{host: host}), do: host
def hostname(url) when is_binary(url), do: hostname(URI.parse(url))
def hostname(nil), do: nil
Note that I chose to use the function head syntax and the :: operator because if we support types only in function heads for now, and if it gets adopted and there is no going back, then we can allow it later in actual function clauses, especially when there is only one clause.
With $ + arrows well it’s a different syntax so it may be harder to inline those in functions, but I guess it’s feasible.
My point is that a type system needs to see functions as a whole ('cause you cannot chose a specific function clause to call from the outside – by other matters than passing the right arguments of course).


















