Specifying a Type

Understand the specific types of Elixir.

A type is simply a subset of all possible values in a language. For example, the type integer means all the possible integer values but excludes lists, binaries, PIDs, and so on.

The basic types in Elixir are as follows: any, atom, float, fun, integer, list, map, maybe_improper_list, none, pid, port, reference, struct, and tuple.

The type any (and its alias, _) is the set of all values, and none is the empty set. A literal atom or integer is the set containing just that value. The value nil can be represented as nil.

Collection types

A list is represented as [type], where type is any of the basic or combined types. This notation doesn’t signify a list of one element. It simply says that elements of the list will be of the given type. If we want to specify a nonempty list, we use [type, ...]. As a convenience, the type list is an alias for [any].

Binaries are represented using this syntax:

  • << >> specifies an empty binary (size 0).

  • << _ :: size >> specifies a sequence of size bits. This is called a bitstring.

  • << _ :: size * unit_size >> specifies a sequence of size units, where each unit is unit_size bits long.

In the last two instances, size can be specified as _, in which case the binary has an arbitrary number of bits/units. The predefined type bitstring is equivalent to <<_::_>>, an arbitrarily sized sequence of bits. Similarly, binary is defined as <<_::_*8>>, an arbitrary sequence of 8-bit bytes.

Tuples are represented as { type, type,... } or by using the type tuple, so both {atom, integer} and tuple(atom, integer} represent a tuple whose first element is an atom and whose second element is an integer.

Combining types

The range operator (..) can be used with literal integers to create a type representing that range. The three built-in types are as follows:

  • The non_neg_integer type represents integers that are greater than or equal to zero.
  • The pos_integer type represents integers that are greater than zero.
  • The neg_integer type represents integers that are less than zero.

The union operator (|) indicates that the acceptable values are the unions of its arguments. Parentheses may be used to group terms in a type specification.

Structures

As structures are basically maps, we could just use the map type for them, but doing so throws away a lot of useful information. Instead, we recommend defining a specific type for each struct:

defmodule LineItem do
  defstruct sku: "", quantity: 1
  @type t :: %LineItem{sku: String.t, quantity: integer}
end

We can then reference this type as LineItem.t.

Anonymous functions

Anonymous functions are specified using (head -> return_type).

The head specifies the arity and possibly the types of the function parameters. We use “...” to mean an arbitrary number of arbitrarily typed arguments or a list of types, in which case the number of types is the function’s arity.

(... -> integer)              # Arbitrary parameters: returns an integer
(list(integer) -> integer)    # Takes a list of integers and returns an integer
(() -> String.t)              # Takes no parameter and returns an Elixir string
(integer, atom -> list(atom)) # Takes an integer and an atom and returns
                              # a list of atoms

We can put parentheses around the head if we find it clearer:

( atom, float -> list )
( (atom, float) -> list )
(list(integer) -> integer)
((list(integer)) -> integer)

Handling truthy values

The type as_boolean(T) says that the actual value matched will be of type T, but the function that uses the value will treat it as a truthy value (anything other than nil or false is considered true). Therefore, the specification for the Elixir function Enum.count is the following:

@spec count(t, (element -> as_boolean(term))) :: non_neg_integer

Some examples

  • integer | float specifies any number (Elixir has an alias for this).

  • [ {atom, any} ] list(atom, any) specifies a list of key-value pairs. The two forms are the same.

  • non_neg_integer | {:error, String.t} specifies an integer greater than or equal to zero, or a tuple containing the atom :error and a string.

  • ( integer, atom -> { :pair, atom, integer } ) specifies an anonymous function that takes an integer and an atom and returns a tuple containing the atom :pair, an atom, and an integer.

  • << _ :: _ * 4 >> specifies a sequence of 4-bit nibbles.

Get hands-on with 1200+ tech skills courses.