Typespecs, Dialyzer & the Type System
Common types: any(), term(), atom(), boolean(), integer(), pos_integer(), non_neg_integer(), float(), number(), String.t(), binary(), list(t), [t], nonempty_list(t), keyword(), keyword(t), map(), %{key: t}, %{optional(k) => v}, %User{}, tuple(), {t1, t2}, f…
23.1 Typespecs
Common types: any(), term(), atom(), boolean(), integer(), pos_integer(), non_neg_integer(), float(), number(), String.t(), binary(), list(t), [t], nonempty_list(t), keyword(), keyword(t), map(), %{key: t}, %{optional(k) => v}, %User{}, tuple(), {t1, t2}, fun(), (a -> b), pid(), reference(), mfa(), module(), timeout(), no_return(), nil, unions a | b, ranges 1..10, literals :ok.
Struct type convention:
defmodule Shapes do
@type shape :: {:circle, radius :: number()} | {:rect, number(), number()}
@typep internal :: map()
@opaque handle :: reference()
@spec area(shape()) :: float()
def area({:circle, r}), do: :math.pi() * r * r
def area({:rect, w, h}), do: w * h * 1.0
end@type t :: %__MODULE__{name: String.t(), age: non_neg_integer()}23.2 Dialyzer
Static analysis via success typings:
# mix.exs deps
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false}mix dialyzer23.3 The built-in gradual type system (1.17+)
Elixir is gaining a set-theoretic gradual type system. Since 1.17 the compiler infers types from patterns and guards and warns about obvious type violations (e.g., calling a function on a struct field that doesn't exist, comparing incompatible types, impossible clauses). 1.18 extended checking to function calls and protocol dispatch; later versions add inference across function boundaries. No annotations are required yet — future versions will add type signatures.
def example(%Date{} = d), do: d.hour # compiler warning: Date has no :hour field