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.
A list is represented as [type], where type is any of the basic or combined types. This notation does not signify a list of one element—it simply says that elements of the list will be of the given type. If you want to specify a nonempty list, use [type, ...]. As a convenience, the type list is an alias for [any].
Binaries are represented using this syntax:
An empty binary (size 0).
A sequence of size bits. This is called a bitstring.
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 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.
The range operator (..) can be used with literal integers to create a type representing that range. The three built-in types, non_neg_integer, pos_integer, and neg_integer, represent integers that are greater than or equal to, greater than, or less than zero, respectively.
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.
As structures are basically maps, you could just use the map type for them, but doing so throws away a lot of useful information. Instead, I recommend that you define a specific type for each struct:
| defmodule LineItem do |
| defstruct sku: "", quantity: 1 |
| @type t :: %LineItem{sku: String.t, quantity: integer} |
| end |
You can then reference this type as LineItem.t.
Anonymous functions are specified using (head -> return_type).
The head specifies the arity and possibly the types of the function parameters. 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 parameters and returns an Elixir string |
| (integer, atom -> list(atom)) # Takes an integer and an atom and returns |
| # a list of atoms |
You can put parentheses around the head if you find it clearer:
| ( atom, float -> list ) |
| ( (atom, float) -> list ) |
| (list(integer) -> integer) |
| ((list(integer)) -> integer) |
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). Thus the specification for the Elixir function Enum.count is
| @spec count(t, (element -> as_boolean(term))) :: non_neg_integer |
Any number (Elixir has an alias for this).
A list of key/value pairs. The two forms are the same.
An integer greater than or equal to zero, or a tuple containing the atom :error and a string.
An anonymous function that takes an integer and an atom and returns a tuple containing the atom :pair, an atom, and an integer.
A sequence of 4-bit nibbles.