Elixir ist eine dynamisch typisierte Sprache. Das bedeutet, dass sie keine Typprüfungen zur Kompilierzeit bietet. Trotzdem können Typspezifikationen nützlich sein, denn sie:
Eine Typspezifikation kannst du einer Funktion mit dem Modulattribut @spec direkt vor der Funktionsdefinition hinzufügen.
@spec longer_than?(String.t(), non_neg_integer()) :: boolean()
def longer_than?(string, length), do: String.length(string) > length
Ausdrücke, die in einer Typspezifikation erlaubt sind:
Grundlegende Typen, zum Beispiel:
boolean()integer(), non_neg_integer(), pos_integer(), float()
list()map()any()Eine Vereinigung von Typen:
integer() | list(integer())
Parametrisierte Typen:
list(integer()) - eine Liste von Ganzzahlen%{age: integer()} - Map mit einer Ganzzahl als Wert unter dem Schlüssel :age
Remote-Typen (die in einem Modul definiert sind), zum Beispiel:
Literale, zum Beispiel:
%{} - eine leere Map[] - eine leere Liste (aber [any()] ist eine nicht leere Liste):ok - ein Atom-LiteralEingebaute spezialisierte Typen, zum Beispiel:
char() - eine Ganzzahl aus dem Bereich 0..0x10FFFF
charlist() - eine Liste von Charskeyword() - eine Liste von Tupeln mit zwei Elementen, wobei das erste Element ein Atom istBenutzerdefinierte Typen
Eine vollständige Liste aller Typen findest du im Abschnitt "Typespecs" der offiziellen Dokumentation.
Argumente in der Typspezifikation können auch benannt werden. Das ist nützlich, um mehrere Argumente desselben Typs zu unterscheiden.
@spec to_hex({hue :: integer, saturation :: integer, lightness :: integer}) :: String.t()
Benutzerdefinierte Typen kannst du mit einem der drei Modulattribute definieren:
@type - definiert einen öffentlichen Typ@typep - definiert einen privaten Typ@opaque - definiert einen öffentlichen Typ, dessen Struktur privat ist@type color :: {hue :: integer, saturation :: integer, lightness :: integer}
@spec to_hex(color()) :: String.t()
String.t() vs binary() vs string()
String.t() ist der richtige Typ für Elixir-Strings, also UTF-8-kodierte Binaries. Technisch gesehen ist String.t() als binary() definiert, und für Analysetools sind diese beiden Typen gleichwertig. Für die Dokumentation von Funktionen, die mit Elixir-Strings arbeiten, ist String.t() jedoch die Wahl, die die Absicht besser erkennen lässt.
Andererseits ist string() ein anderer Typ. Es ist ein Erlang-String, in Elixir als Charlist bekannt. Der Typ string() sollte in Typspezifikationen vermieden werden; stattdessen solltest du charlist() verwenden.
Dialyzer ist ein statisches Analysetool, das Probleme wie Typfehler in Erlang- und Elixir-Code erkennen kann. Der einfachste Weg, Dialyzer in einem Elixir-Projekt zu nutzen, ist Dialyxir.