Elixir es un lenguaje de tipado dinámico, lo que significa que no proporciona comprobaciones de tipos en tiempo de compilación. Aun así, las especificaciones de tipo pueden ser útiles porque:
Se puede añadir una especificación de tipo a una función usando el atributo de módulo @spec justo antes de la definición de la función.
@spec longer_than?(String.t(), non_neg_integer()) :: boolean()
def longer_than?(string, length), do: String.length(string) > length
Expresiones permitidas en una especificación de tipo:
Tipos básicos, por ejemplo:
boolean()integer(), non_neg_integer(), pos_integer(), float()
list()map()any()Una unión de tipos:
integer() | list(integer())
Tipos parametrizados:
list(integer()), una lista de enteros%{age: integer()}, un mapa con un valor entero bajo la clave :age
Tipos remotos (definidos en algún módulo), por ejemplo:
Literales, por ejemplo:
%{}, un mapa vacío[], una lista vacía (pero [any()] es una lista no vacía):ok, un literal de átomoTipos especializados integrados, por ejemplo:
char(), un entero del rango 0..0x10FFFF
charlist(), una lista de caractereskeyword(), una lista de tuplas de dos elementos, donde el primer elemento es un átomoTipos personalizados
Puedes encontrar una lista completa de todos los tipos en la sección «Typespecs» de la documentación oficial.
Los argumentos de la especificación de tipo también pueden nombrarse, lo que resulta útil para distinguir varios argumentos del mismo tipo.
@spec to_hex({hue :: integer, saturation :: integer, lightness :: integer}) :: String.t()
Los tipos personalizados se pueden definir usando uno de estos tres atributos de módulo:
@type, define un tipo público@typep, define un tipo privado@opaque, define un tipo público cuya estructura es privada@type color :: {hue :: integer, saturation :: integer, lightness :: integer}
@spec to_hex(color()) :: String.t()
String.t() frente a binary() frente a string()
String.t() es el tipo correcto para los strings de Elixir, que son binarios codificados en UTF-8. Técnicamente, String.t() se define como un binary(), y esos dos tipos son equivalentes para las herramientas de análisis, pero String.t() es la opción que revela mejor la intención a la hora de documentar funciones que trabajan con strings de Elixir.
En cambio, string() es un tipo distinto. Es un string de Erlang, conocido en Elixir como charlist. Conviene evitar el tipo string() en las especificaciones de tipo y usar charlist() en su lugar.
Dialyzer es una herramienta de análisis estático que puede detectar problemas como errores de tipos en código Erlang y Elixir. La forma más sencilla de usar Dialyzer en un proyecto de Elixir es con Dialyxir.