Tracks
/
Elixir
Elixir
/
Syllabus
/
Typespecs
Ty

Typespecs in Elixir

1 exercise

About Typespecs

Elixir is a dynamically typed language, which means it doesn't provide compile-time type checks. Still, type specifications can be useful because they:

  • Serve as documentation.
  • Can be used by static analysis tools like Dialyzer to find possible bugs.

A type specification can be added to a function using the @spec module attribute right before the function definition.

@spec longer_than?(String.t(), non_neg_integer()) :: boolean()
def longer_than?(string, length), do: String.length(string) > length

Types

Expressions allowed in a typespec:

  • Basic types, for example:

    • boolean()
    • integer(), non_neg_integer(), pos_integer(), float()
    • list()
    • map()
    • any()
  • A union of types:

    • e.g. integer() | list(integer())
  • Parameterized types:

    • e.g. list(integer()) - a list of integers
    • e.g. %{age: integer()} - map with an integer value under the key :age
  • Remote types (defined in some module), for example:

  • Literals, for example:

    • %{} - an empty map
    • [] - an empty list (but [any()] is a non-empty list)
    • e.g. :ok - an atom literal
  • Built-in specialized types, for example:

    • char() - an integer from the range 0..0x10FFFF
    • charlist() - a list of chars
    • keyword() - a list of two element tuples, where the first element is an atom
  • Custom types

A full list of all types can be found in the "Typespecs" section in the official documentation.

Naming arguments

Arguments in the typespec could also be named which is useful for distinguishing multiple arguments of the same type.

@spec to_hex({hue :: integer, saturation :: integer, lightness :: integer}) :: String.t()

Custom types

Custom types can be defined in using one of the three module attributes:

  • @type - defines a public type
  • @typep - defines a private type
  • @opaque - defines a public type whose structure is private
@type color :: {hue :: integer, saturation :: integer, lightness :: integer}

@spec to_hex(color()) :: String.t()

String.t() vs binary() vs string()

String.t() is the correct type to use for Elixir strings, which are UTF-8 encoded binaries. Technically, String.t() is defined as a binary(), and those two types are equivalent to analysis tools, but String.t() is the more intention-revealing choice for documenting functions that work with Elixir strings.

On the other hand, string() is a different type. It's an Erlang string, in Elixir known as a charlist. The string() type should be avoided in typespecs and charlist() should be used instead.

Dialyzer

Dialyzer is a static analysis tool that can detect problems such as type errors in Erlang and Elixir code. The easiest way to use Dialyzer in an Elixir project is with Dialyxir.

Edit via GitHub The link opens in a new window or tab

Learn Typespecs