Elixir macht es Entwicklern einfacher, dem Paradigma der funktionalen Programmierung treu zu bleiben.Maya Kruchankova | shutterstock.com Elixir ist eine vergleichsweise junge Programmiersprache, die 2012 veröffentlicht wurde und auf dem Erlang-Ökosystem aufsetzt. Dabei realisiert sie eine neue Herangehensweise an die funktionale Programmierung und denkt Concurrency und Fehlertoleranz neu. Dieses Grundlagen-Tutorial bietet Ihnen zunächst einen kurzen Überblick über die syntaktische Struktur von Elixir. Im Anschluss erhalten Sie Einblicke in die Themenfelder: Collections, Maps und Atoms, Modules, Pattern Matching, Loops, und Concurrency. Elixir-Grundlagen Zunächst müssen Sie Elixir installieren. Das verleiht Ihnen Zugriff auf das Kommandozeilen-Interface IEx, mit dem Sie Elixir im REPL-Modus ausführen können. Jede Sprache hat ihre eigenen Konventionen für alltägliche Dinge – etwa Slashes für Kommentare. Mit der Verkettung von Strings geht Elixir folgendermaßen um: “Hello ” “InfoWorld” Dieser Ansatz stellt einen eindeutigen Operator zur Verfügung, statt das Plus-Symbol zu überladen. Um den Standard-Output zu erzeugen, nutzen Sie folgenden Befehl: IO.puts(“Hello ” “InfoWorld”) Mit IO.puts() erstellen Sie in Elixir Konsolen-Outputs. Falls Sie eine Variable für diese Zeichenkette erstellen wollen, benötigen Sie im Fall von Elixir keine Keywords – stattdessen deklarieren Sie sie einfach: my_var = “Hello Infoworld” Collections in Elixir Werfen wir nun einen Blick darauf, wie Elixir mit Collections umgeht: books = [ “The Bhagavad Gita”, “Tao Te Ching”, “The Bible”, “The Quran”, ” Diese Array-Syntax (technisch gesehen handelt es sich dabei in Elixir um eine List) ist so universell, dass sie keiner Erklärung bedarf. Allerdings ist dabei zu beachten, dass Arrays in Elixir unveränderlich sind. Dieser Grundsatz der funktionalen Programmierung gestaltet das Gesamtsystem der Programmiersprache weniger fehleranfällig – und zahlt auf die Concurrency ein. Trotzdem können Sie mit der Elixir-Syntax einige Operationen wie add und remove so ausführen, dass sie veränderlich erscheinen. Im Hintergrund wird dabei eine neue List erstellt: books ++ [“The Gospel of John in the Light of Indian Mysticism”] Unveränderliche CollectionsUnveränderliche Collections wirken sich nachteilig auf die Performance aus. Die zugrundeliegende virtuelle Maschine kann das mit einigen Tricks abmildern – etwa „Structural Sharing“ (statt Daten zu kopieren, werden Pointer modifiziert) oder Erlang Term Storage (kann zum Einsatz kommen, wenn große Collections modifiziert werden sollen). Um Collections in-line zu verarbeiten, kann in vielen Fällen Streaming zum Einsatz kommen. Wollen Sie nun die Buchstaben in jedem einzelnen String zählen und aus diesen Ganzzahlen eine Collection erstellen, können Sie dazu den “Zauber” funktionaler Programmierung einsetzen: book_lengths = Enum.map(books, &String.length/1) Die map-Funktion weit verbreitet: In JavaScript kommt sie zum Einsatz, um eine bestimmte Funktion über eine Collection auszuführen. Elixir führt hingegen einige einzigartige map-Funktionen ein: /1 gibt an, welche überladene Version von String.length verwendet werden soll. Bei & handelt es sich um den sogenannten „Function Capture“-Operator, der Zugriff auf die Funktion String.length ermöglicht (in etwa vergleichbar mit dem Methodenreferenz-Operator in Java „::“). Um einen gefälligen Output zu erzeugen, nutzen Sie die IO.inspect-Funktion: IO.inspect(book_lengths) [17, 12, 9, 9, 21] Elixirs Collection-Typen Nachdem wir List bereits in Aktion gesehen, hier alle wesentlichen Collection-Typen in Elixir im Überblick: List: Unveränderliche, homogene Collection beliebiger Typen, die für eine Modifizierung durch Duplizierung ausgelegt ist. Die Syntax umschließt Elemente mit eckigen Klammern – [x,y,z]. Tupel: Tupel sind in erster Linie darauf konzipiert, Werte vorzuhalten – nicht zu verändern. Sie ähneln Lists, sind jedoch auf Read-Performance ausgelegt. Die Syntax umschließt Elemente mit geschweiften Klammern – {x,y,z}. Keyword-List: Diese wird hauptsächlich für benannte Argumente von Funktionen genutzt und enthält geordnete, „String-only“ Key-Value-Paare. Die Syntax umschließt diese Paare mit eckigen Klammern – [x: x1, y: y1, z:z1]. Maps: Die bekannten Key-Value-Paare – wobei Key alles sein kann und die Collection ungeordnet ist. Die Syntax umschließt die Key-Value-Paare mit einem Prozent-Symbol und geschweiften Klammern – %{x: x1, y: y1, z: z1}, %{x => x1, y => y1, z => z1}. Maps und Atoms in Elixir Maps können in Elixir auf zweierlei Arten deklariert werden – abhängig davon, ob es sich bei den Keys um sogenannte Atoms handelt oder nicht. Dieser Begriff bezeichnet eine Variable, deren Wert mit ihrem Namen identisch ist – eine Art Superkonstante. Deklariert wird ein Atom mit einem Doppelpunkt, gefolgt von seinem Namen. Eine Map von Strings zu Ganzzahlenwerten erstellen Sie folgendermaßen: books_and_lengths = %{ “The Bhagavad Gita” => 17, “Tao Te Ching” => 12 } Das ist etwas anderes als der folgende Code, der eine Map von Atoms zu Ganzzahlen realisiert (was in diesem Fall wahrscheinlich nicht zum Ziel führt): books_and_lengths = %{ “The Bhagavad Gita”: 17, “Tao Te Ching”: 12 } Achten Sie in diesem Beispiel auf die Platzierung des Doppelpunkts: In einer Map zeigt der Doppelpunkt direkt neben dem Key an, dass es sich um ein Atom handelt (diese können in Anführungszeichen gesetzt werden, um ansonsten unzulässige Zeichen zu unterstützen). Grundsätzlich gilt: für normale Variablen nutzen Sie die Pfeilsyntax (=>), für Atoms Key und Doppelpunkt (:). Atoms werden für gewöhnlich folgendermaßen deklariert: :my_atom Eine weitere Möglichkeit, eine Map mit Atom Keys zu deklarieren: my_map = %{:atom1 => “foo”, :atom2 => “bar”} Elixir-Modules Elixir unterstützt Modules (Module). Diese stellen Namespaces dar, die zusammengehörige Funktionen zusammenfassen, jedoch weder State noch Variablen erfassen. Erwartungsgemäß ermöglicht Ihnen das, andere Funktionen innerhalb desselben Moduls aufzurufen. Wird der Call von außen initiiert, ist ein Präfix voranzustellen oder das Modul zu importieren. Ein simples Beispiel für ein Module in Elixir: defmodule BookFunctions do def myFunc end end BookFunctions.myFunc() Pattern Matching in Elixir Syntaktische Feinheiten und die Eigenschaften von Standard-Bibliotheken tragen ganz wesentlich zum übergreifenden „Look & Feel“ einer Programmiersprache bei – schließlich handelt es sich dabei um alltägliche Funktionen, mit denen die Entwickler kontinuierlich interagieren. Dabei hat jede Sprache einige einzigartige Features an Bord. Im Fall von Elixir ist hier insbesondere Pattern Matching zu nennen. Diese Funktion ermöglicht Ihnen, konditionale Funktionen mit einer Switch-ähnlichen Syntax auszuführen. Angenommen, Sie wollen in unserem Beispiel die Buchtitel nach Länge sortieren und sie in drei Kategorien ausgeben (small, medium, long) – dazu gehen Sie folgendermaßen vor: defmodule BookFunctions do def categorize_length(length) do case length do length when length “Short” length when length <= 20 -> “Medium” _ -> “Long” end end def print_categories(lengths) do Enum.each(lengths, fn length -> category = categorize_length(length) IO.puts(“#{length} characters: #{category}”) end) end end Ein paar Anmerkungen zu diesem Code: Bei BookFunctions handelt es sich um ein Module. Return Statements sind in Elixir implizit – die Funktion categorize_length() gibt das Ergebnis des letzten Ausdrucks automatisch zurück. Das Keyword case erstellt den Pattern-Matching-Block in der categorize_length-Funktion. Die length when length-Syntax (technisch gesehen eine „Guard Clause“) ermöglicht einen Range Check der Längenvariable. Erfüllt sie die Kriterien, teilt der ->-Operator mit, was zurückgegeben werden soll. Da es sich um die letzte Anweisung der Funktion handelt, entspricht sie auch dem funktionalen Rückgabewert.) Diese neue Funktion könnten Sie nun auf book_lengths anwenden: BookBookFunctions.print_categories(book_lengths) 17 characters: Medium 12 characters: Medium 9 characters: Short 9 characters: Short 21 characters: Long Enum.each ist analog zu forEach in anderen Sprachen wie JavaScript und ermöglicht, eine Operation für jedes Element einer Collection auszuführen. Loops in Elixir In Elixir existieren weder for- noch while-Loops. Das deckt sich mit dem Grundsatz der Unveränderlichkeit, der bei der funktionalen Programmierung im Mittelpunkt steht. Stattdessen setzt Elixir auf Recursion. Um Loops umzusetzen, können Sie bei Elixir deshalb auf funktionale Operatoren wie Enum.each und Enum.map zurückgreifen. Comprehensions Eine Möglichkeit, einen for-Loop zu simulieren, bieten Comprehensions (die „echten“ for-Loops zum Verwechseln ähnlich sehen): for x Die Elixir-Dokumentation hält weitere Beispiele und Informationen zu diesem Thema bereit. Pipe-Operator Elixirs Pipe-Operator liefert Ihnen eine saubere Syntax, um Funktionsergebnisse miteinander zu verknüpfen. Das könnte man auch als eine elegantere Form von Funktions-Nesting bezeichnen. Ein simples Pipe-Operator-Beispiel für unsere books_and_lengths-Collection: books_and_lengths |> Map.keys() |> Enum.map(&String.upcase/1) |> Enum.join(“, “) |> IO.puts() Der Output sieht folgendermaßen aus: The Bhagavad Gita, Tao Te Ching Concurrency in Elixir Eine weitere Stärke von Elixir ist Concurrency: In der Programmiersprache kommen sogenannte „Actors“ zum Einsatz, die so etwas wie virtuelle Threads darstellen (weil sie keine vollständigen Betriebssystemprozesse sind) und über „Message Passing“ eine vereinfachte, gleichzeitige Kommunikation ermöglichen. Folgendes Code-Beispiel entstammt der Elixir-Dokumentation und veranschaulicht Message Handling: defmodule Example do def listen do receive do {:ok, “hello”} -> IO.puts(“World”) end listen() end end Mit Blick auf dieses Code-Beispiel ist zu beachten, dass die listen-Funktion rekursiv ist – sie ruft sich am Ende selbst auf. Ohne sie würde der Prozess beendet. Um diesen Actor zu starten, nutzen wir spawn: pid = spawn(Example, :listen, []) Anschließend können wir eine Nachricht aus dem Hauptprozess senden, indem wir die gespeicherte pid verwenden: send pid, {:ok, “hello”} Das sorgt für den Konsolen-Output „World“. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!
Elixir-Tutorial: Funktionale Programmierung neu gedacht
Elixir macht es Entwicklern einfacher, dem Paradigma der funktionalen Programmierung treu zu bleiben.Maya Kruchankova | shutterstock.com Elixir ist eine vergleichsweise junge Programmiersprache, die 2012 veröffentlicht wurde und auf dem Erlang-Ökosystem aufsetzt. Dabei realisiert sie eine neue Herangehensweise an die funktionale Programmierung und denkt Concurrency und Fehlertoleranz neu. Dieses Grundlagen-Tutorial bietet Ihnen zunächst einen kurzen Überblick über die syntaktische Struktur von Elixir. Im Anschluss erhalten Sie Einblicke in die Themenfelder: Collections, Maps und Atoms, Modules, Pattern Matching, Loops, und Concurrency. Elixir-Grundlagen Zunächst müssen Sie Elixir installieren. Das verleiht Ihnen Zugriff auf das Kommandozeilen-Interface IEx, mit dem Sie Elixir im REPL-Modus ausführen können. Jede Sprache hat ihre eigenen Konventionen für alltägliche Dinge – etwa Slashes für Kommentare. Mit der Verkettung von Strings geht Elixir folgendermaßen um: "Hello " "InfoWorld" Dieser Ansatz stellt einen eindeutigen Operator zur Verfügung, statt das Plus-Symbol zu überladen. Um den Standard-Output zu erzeugen, nutzen Sie folgenden Befehl: IO.puts("Hello " "InfoWorld") Mit IO.puts() erstellen Sie in Elixir Konsolen-Outputs. Falls Sie eine Variable für diese Zeichenkette erstellen wollen, benötigen Sie im Fall von Elixir keine Keywords – stattdessen deklarieren Sie sie einfach: my_var = “Hello Infoworld” Collections in Elixir Werfen wir nun einen Blick darauf, wie Elixir mit Collections umgeht: books = [ "The Bhagavad Gita", "Tao Te Ching", "The Bible", "The Quran", " Diese Array-Syntax (technisch gesehen handelt es sich dabei in Elixir um eine List) ist so universell, dass sie keiner Erklärung bedarf. Allerdings ist dabei zu beachten, dass Arrays in Elixir unveränderlich sind. Dieser Grundsatz der funktionalen Programmierung gestaltet das Gesamtsystem der Programmiersprache weniger fehleranfällig – und zahlt auf die Concurrency ein. Trotzdem können Sie mit der Elixir-Syntax einige Operationen wie add und remove so ausführen, dass sie veränderlich erscheinen. Im Hintergrund wird dabei eine neue List erstellt: books ++ ["The Gospel of John in the Light of Indian Mysticism"] Unveränderliche CollectionsUnveränderliche Collections wirken sich nachteilig auf die Performance aus. Die zugrundeliegende virtuelle Maschine kann das mit einigen Tricks abmildern – etwa „Structural Sharing“ (statt Daten zu kopieren, werden Pointer modifiziert) oder Erlang Term Storage (kann zum Einsatz kommen, wenn große Collections modifiziert werden sollen). Um Collections in-line zu verarbeiten, kann in vielen Fällen Streaming zum Einsatz kommen. Wollen Sie nun die Buchstaben in jedem einzelnen String zählen und aus diesen Ganzzahlen eine Collection erstellen, können Sie dazu den “Zauber” funktionaler Programmierung einsetzen: book_lengths = Enum.map(books, &String.length/1) Die map-Funktion weit verbreitet: In JavaScript kommt sie zum Einsatz, um eine bestimmte Funktion über eine Collection auszuführen. Elixir führt hingegen einige einzigartige map-Funktionen ein: /1 gibt an, welche überladene Version von String.length verwendet werden soll. Bei & handelt es sich um den sogenannten „Function Capture“-Operator, der Zugriff auf die Funktion String.length ermöglicht (in etwa vergleichbar mit dem Methodenreferenz-Operator in Java „::“). Um einen gefälligen Output zu erzeugen, nutzen Sie die IO.inspect-Funktion: IO.inspect(book_lengths) [17, 12, 9, 9, 21] Elixirs Collection-Typen Nachdem wir List bereits in Aktion gesehen, hier alle wesentlichen Collection-Typen in Elixir im Überblick: List: Unveränderliche, homogene Collection beliebiger Typen, die für eine Modifizierung durch Duplizierung ausgelegt ist. Die Syntax umschließt Elemente mit eckigen Klammern – [x,y,z]. Tupel: Tupel sind in erster Linie darauf konzipiert, Werte vorzuhalten – nicht zu verändern. Sie ähneln Lists, sind jedoch auf Read-Performance ausgelegt. Die Syntax umschließt Elemente mit geschweiften Klammern – {x,y,z}. Keyword-List: Diese wird hauptsächlich für benannte Argumente von Funktionen genutzt und enthält geordnete, „String-only“ Key-Value-Paare. Die Syntax umschließt diese Paare mit eckigen Klammern – [x: x1, y: y1, z:z1]. Maps: Die bekannten Key-Value-Paare – wobei Key alles sein kann und die Collection ungeordnet ist. Die Syntax umschließt die Key-Value-Paare mit einem Prozent-Symbol und geschweiften Klammern – %{x: x1, y: y1, z: z1}, %{x => x1, y => y1, z => z1}. Maps und Atoms in Elixir Maps können in Elixir auf zweierlei Arten deklariert werden – abhängig davon, ob es sich bei den Keys um sogenannte Atoms handelt oder nicht. Dieser Begriff bezeichnet eine Variable, deren Wert mit ihrem Namen identisch ist – eine Art Superkonstante. Deklariert wird ein Atom mit einem Doppelpunkt, gefolgt von seinem Namen. Eine Map von Strings zu Ganzzahlenwerten erstellen Sie folgendermaßen: books_and_lengths = %{ "The Bhagavad Gita" => 17, "Tao Te Ching" => 12 } Das ist etwas anderes als der folgende Code, der eine Map von Atoms zu Ganzzahlen realisiert (was in diesem Fall wahrscheinlich nicht zum Ziel führt): books_and_lengths = %{ "The Bhagavad Gita": 17, "Tao Te Ching": 12 } Achten Sie in diesem Beispiel auf die Platzierung des Doppelpunkts: In einer Map zeigt der Doppelpunkt direkt neben dem Key an, dass es sich um ein Atom handelt (diese können in Anführungszeichen gesetzt werden, um ansonsten unzulässige Zeichen zu unterstützen). Grundsätzlich gilt: für normale Variablen nutzen Sie die Pfeilsyntax (=>), für Atoms Key und Doppelpunkt (:). Atoms werden für gewöhnlich folgendermaßen deklariert: :my_atom Eine weitere Möglichkeit, eine Map mit Atom Keys zu deklarieren: my_map = %{:atom1 => “foo”, :atom2 => “bar”} Elixir-Modules Elixir unterstützt Modules (Module). Diese stellen Namespaces dar, die zusammengehörige Funktionen zusammenfassen, jedoch weder State noch Variablen erfassen. Erwartungsgemäß ermöglicht Ihnen das, andere Funktionen innerhalb desselben Moduls aufzurufen. Wird der Call von außen initiiert, ist ein Präfix voranzustellen oder das Modul zu importieren. Ein simples Beispiel für ein Module in Elixir: defmodule BookFunctions do def myFunc end end BookFunctions.myFunc() Pattern Matching in Elixir Syntaktische Feinheiten und die Eigenschaften von Standard-Bibliotheken tragen ganz wesentlich zum übergreifenden „Look & Feel“ einer Programmiersprache bei – schließlich handelt es sich dabei um alltägliche Funktionen, mit denen die Entwickler kontinuierlich interagieren. Dabei hat jede Sprache einige einzigartige Features an Bord. Im Fall von Elixir ist hier insbesondere Pattern Matching zu nennen. Diese Funktion ermöglicht Ihnen, konditionale Funktionen mit einer Switch-ähnlichen Syntax auszuführen. Angenommen, Sie wollen in unserem Beispiel die Buchtitel nach Länge sortieren und sie in drei Kategorien ausgeben (small, medium, long) – dazu gehen Sie folgendermaßen vor: defmodule BookFunctions do def categorize_length(length) do case length do length when length "Short" length when length <= 20 -> "Medium" _ -> "Long" end end def print_categories(lengths) do Enum.each(lengths, fn length -> category = categorize_length(length) IO.puts("#{length} characters: #{category}") end) end end Ein paar Anmerkungen zu diesem Code: Bei BookFunctions handelt es sich um ein Module. Return Statements sind in Elixir implizit – die Funktion categorize_length() gibt das Ergebnis des letzten Ausdrucks automatisch zurück. Das Keyword case erstellt den Pattern-Matching-Block in der categorize_length-Funktion. Die length when length-Syntax (technisch gesehen eine „Guard Clause“) ermöglicht einen Range Check der Längenvariable. Erfüllt sie die Kriterien, teilt der ->-Operator mit, was zurückgegeben werden soll. Da es sich um die letzte Anweisung der Funktion handelt, entspricht sie auch dem funktionalen Rückgabewert.) Diese neue Funktion könnten Sie nun auf book_lengths anwenden: BookBookFunctions.print_categories(book_lengths) 17 characters: Medium 12 characters: Medium 9 characters: Short 9 characters: Short 21 characters: Long Enum.each ist analog zu forEach in anderen Sprachen wie JavaScript und ermöglicht, eine Operation für jedes Element einer Collection auszuführen. Loops in Elixir In Elixir existieren weder for- noch while-Loops. Das deckt sich mit dem Grundsatz der Unveränderlichkeit, der bei der funktionalen Programmierung im Mittelpunkt steht. Stattdessen setzt Elixir auf Recursion. Um Loops umzusetzen, können Sie bei Elixir deshalb auf funktionale Operatoren wie Enum.each und Enum.map zurückgreifen. Comprehensions Eine Möglichkeit, einen for-Loop zu simulieren, bieten Comprehensions (die „echten“ for-Loops zum Verwechseln ähnlich sehen): for x Die Elixir-Dokumentation hält weitere Beispiele und Informationen zu diesem Thema bereit. Pipe-Operator Elixirs Pipe-Operator liefert Ihnen eine saubere Syntax, um Funktionsergebnisse miteinander zu verknüpfen. Das könnte man auch als eine elegantere Form von Funktions-Nesting bezeichnen. Ein simples Pipe-Operator-Beispiel für unsere books_and_lengths-Collection: books_and_lengths |> Map.keys() |> Enum.map(&String.upcase/1) |> Enum.join(", ") |> IO.puts() Der Output sieht folgendermaßen aus: The Bhagavad Gita, Tao Te Ching Concurrency in Elixir Eine weitere Stärke von Elixir ist Concurrency: In der Programmiersprache kommen sogenannte „Actors“ zum Einsatz, die so etwas wie virtuelle Threads darstellen (weil sie keine vollständigen Betriebssystemprozesse sind) und über „Message Passing“ eine vereinfachte, gleichzeitige Kommunikation ermöglichen. Folgendes Code-Beispiel entstammt der Elixir-Dokumentation und veranschaulicht Message Handling: defmodule Example do def listen do receive do {:ok, "hello"} -> IO.puts("World") end listen() end end Mit Blick auf dieses Code-Beispiel ist zu beachten, dass die listen-Funktion rekursiv ist – sie ruft sich am Ende selbst auf. Ohne sie würde der Prozess beendet. Um diesen Actor zu starten, nutzen wir spawn: pid = spawn(Example, :listen, []) Anschließend können wir eine Nachricht aus dem Hauptprozess senden, indem wir die gespeicherte pid verwenden: send pid, {:ok, "hello"} Das sorgt für den Konsolen-Output „World“. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!
Elixir-Tutorial: Funktionale Programmierung neu gedacht Elixir macht es Entwicklern einfacher, dem Paradigma der funktionalen Programmierung treu zu bleiben.Maya Kruchankova | shutterstock.com Elixir ist eine vergleichsweise junge Programmiersprache, die 2012 veröffentlicht wurde und auf dem Erlang-Ökosystem aufsetzt. Dabei realisiert sie eine neue Herangehensweise an die funktionale Programmierung und denkt Concurrency und Fehlertoleranz neu. Dieses Grundlagen-Tutorial bietet Ihnen zunächst einen kurzen Überblick über die syntaktische Struktur von Elixir. Im Anschluss erhalten Sie Einblicke in die Themenfelder: Collections, Maps und Atoms, Modules, Pattern Matching, Loops, und Concurrency. Elixir-Grundlagen Zunächst müssen Sie Elixir installieren. Das verleiht Ihnen Zugriff auf das Kommandozeilen-Interface IEx, mit dem Sie Elixir im REPL-Modus ausführen können. Jede Sprache hat ihre eigenen Konventionen für alltägliche Dinge – etwa Slashes für Kommentare. Mit der Verkettung von Strings geht Elixir folgendermaßen um: "Hello " "InfoWorld" Dieser Ansatz stellt einen eindeutigen Operator zur Verfügung, statt das Plus-Symbol zu überladen. Um den Standard-Output zu erzeugen, nutzen Sie folgenden Befehl: IO.puts("Hello " "InfoWorld") Mit IO.puts() erstellen Sie in Elixir Konsolen-Outputs. Falls Sie eine Variable für diese Zeichenkette erstellen wollen, benötigen Sie im Fall von Elixir keine Keywords – stattdessen deklarieren Sie sie einfach: my_var = “Hello Infoworld” Collections in Elixir Werfen wir nun einen Blick darauf, wie Elixir mit Collections umgeht: books = [ "The Bhagavad Gita", "Tao Te Ching", "The Bible", "The Quran", " Diese Array-Syntax (technisch gesehen handelt es sich dabei in Elixir um eine List) ist so universell, dass sie keiner Erklärung bedarf. Allerdings ist dabei zu beachten, dass Arrays in Elixir unveränderlich sind. Dieser Grundsatz der funktionalen Programmierung gestaltet das Gesamtsystem der Programmiersprache weniger fehleranfällig – und zahlt auf die Concurrency ein. Trotzdem können Sie mit der Elixir-Syntax einige Operationen wie add und remove so ausführen, dass sie veränderlich erscheinen. Im Hintergrund wird dabei eine neue List erstellt: books ++ ["The Gospel of John in the Light of Indian Mysticism"] Unveränderliche CollectionsUnveränderliche Collections wirken sich nachteilig auf die Performance aus. Die zugrundeliegende virtuelle Maschine kann das mit einigen Tricks abmildern – etwa „Structural Sharing“ (statt Daten zu kopieren, werden Pointer modifiziert) oder Erlang Term Storage (kann zum Einsatz kommen, wenn große Collections modifiziert werden sollen). Um Collections in-line zu verarbeiten, kann in vielen Fällen Streaming zum Einsatz kommen. Wollen Sie nun die Buchstaben in jedem einzelnen String zählen und aus diesen Ganzzahlen eine Collection erstellen, können Sie dazu den “Zauber” funktionaler Programmierung einsetzen: book_lengths = Enum.map(books, &String.length/1) Die map-Funktion weit verbreitet: In JavaScript kommt sie zum Einsatz, um eine bestimmte Funktion über eine Collection auszuführen. Elixir führt hingegen einige einzigartige map-Funktionen ein: /1 gibt an, welche überladene Version von String.length verwendet werden soll. Bei & handelt es sich um den sogenannten „Function Capture“-Operator, der Zugriff auf die Funktion String.length ermöglicht (in etwa vergleichbar mit dem Methodenreferenz-Operator in Java „::“). Um einen gefälligen Output zu erzeugen, nutzen Sie die IO.inspect-Funktion: IO.inspect(book_lengths) [17, 12, 9, 9, 21] Elixirs Collection-Typen Nachdem wir List bereits in Aktion gesehen, hier alle wesentlichen Collection-Typen in Elixir im Überblick: List: Unveränderliche, homogene Collection beliebiger Typen, die für eine Modifizierung durch Duplizierung ausgelegt ist. Die Syntax umschließt Elemente mit eckigen Klammern – [x,y,z]. Tupel: Tupel sind in erster Linie darauf konzipiert, Werte vorzuhalten – nicht zu verändern. Sie ähneln Lists, sind jedoch auf Read-Performance ausgelegt. Die Syntax umschließt Elemente mit geschweiften Klammern – {x,y,z}. Keyword-List: Diese wird hauptsächlich für benannte Argumente von Funktionen genutzt und enthält geordnete, „String-only“ Key-Value-Paare. Die Syntax umschließt diese Paare mit eckigen Klammern – [x: x1, y: y1, z:z1]. Maps: Die bekannten Key-Value-Paare – wobei Key alles sein kann und die Collection ungeordnet ist. Die Syntax umschließt die Key-Value-Paare mit einem Prozent-Symbol und geschweiften Klammern – %{x: x1, y: y1, z: z1}, %{x => x1, y => y1, z => z1}. Maps und Atoms in Elixir Maps können in Elixir auf zweierlei Arten deklariert werden – abhängig davon, ob es sich bei den Keys um sogenannte Atoms handelt oder nicht. Dieser Begriff bezeichnet eine Variable, deren Wert mit ihrem Namen identisch ist – eine Art Superkonstante. Deklariert wird ein Atom mit einem Doppelpunkt, gefolgt von seinem Namen. Eine Map von Strings zu Ganzzahlenwerten erstellen Sie folgendermaßen: books_and_lengths = %{ "The Bhagavad Gita" => 17, "Tao Te Ching" => 12 } Das ist etwas anderes als der folgende Code, der eine Map von Atoms zu Ganzzahlen realisiert (was in diesem Fall wahrscheinlich nicht zum Ziel führt): books_and_lengths = %{ "The Bhagavad Gita": 17, "Tao Te Ching": 12 } Achten Sie in diesem Beispiel auf die Platzierung des Doppelpunkts: In einer Map zeigt der Doppelpunkt direkt neben dem Key an, dass es sich um ein Atom handelt (diese können in Anführungszeichen gesetzt werden, um ansonsten unzulässige Zeichen zu unterstützen). Grundsätzlich gilt: für normale Variablen nutzen Sie die Pfeilsyntax (=>), für Atoms Key und Doppelpunkt (:). Atoms werden für gewöhnlich folgendermaßen deklariert: :my_atom Eine weitere Möglichkeit, eine Map mit Atom Keys zu deklarieren: my_map = %{:atom1 => “foo”, :atom2 => “bar”} Elixir-Modules Elixir unterstützt Modules (Module). Diese stellen Namespaces dar, die zusammengehörige Funktionen zusammenfassen, jedoch weder State noch Variablen erfassen. Erwartungsgemäß ermöglicht Ihnen das, andere Funktionen innerhalb desselben Moduls aufzurufen. Wird der Call von außen initiiert, ist ein Präfix voranzustellen oder das Modul zu importieren. Ein simples Beispiel für ein Module in Elixir: defmodule BookFunctions do def myFunc end end BookFunctions.myFunc() Pattern Matching in Elixir Syntaktische Feinheiten und die Eigenschaften von Standard-Bibliotheken tragen ganz wesentlich zum übergreifenden „Look & Feel“ einer Programmiersprache bei – schließlich handelt es sich dabei um alltägliche Funktionen, mit denen die Entwickler kontinuierlich interagieren. Dabei hat jede Sprache einige einzigartige Features an Bord. Im Fall von Elixir ist hier insbesondere Pattern Matching zu nennen. Diese Funktion ermöglicht Ihnen, konditionale Funktionen mit einer Switch-ähnlichen Syntax auszuführen. Angenommen, Sie wollen in unserem Beispiel die Buchtitel nach Länge sortieren und sie in drei Kategorien ausgeben (small, medium, long) – dazu gehen Sie folgendermaßen vor: defmodule BookFunctions do def categorize_length(length) do case length do length when length "Short" length when length <= 20 -> "Medium" _ -> "Long" end end def print_categories(lengths) do Enum.each(lengths, fn length -> category = categorize_length(length) IO.puts("#{length} characters: #{category}") end) end end Ein paar Anmerkungen zu diesem Code: Bei BookFunctions handelt es sich um ein Module. Return Statements sind in Elixir implizit – die Funktion categorize_length() gibt das Ergebnis des letzten Ausdrucks automatisch zurück. Das Keyword case erstellt den Pattern-Matching-Block in der categorize_length-Funktion. Die length when length-Syntax (technisch gesehen eine „Guard Clause“) ermöglicht einen Range Check der Längenvariable. Erfüllt sie die Kriterien, teilt der ->-Operator mit, was zurückgegeben werden soll. Da es sich um die letzte Anweisung der Funktion handelt, entspricht sie auch dem funktionalen Rückgabewert.) Diese neue Funktion könnten Sie nun auf book_lengths anwenden: BookBookFunctions.print_categories(book_lengths) 17 characters: Medium 12 characters: Medium 9 characters: Short 9 characters: Short 21 characters: Long Enum.each ist analog zu forEach in anderen Sprachen wie JavaScript und ermöglicht, eine Operation für jedes Element einer Collection auszuführen. Loops in Elixir In Elixir existieren weder for- noch while-Loops. Das deckt sich mit dem Grundsatz der Unveränderlichkeit, der bei der funktionalen Programmierung im Mittelpunkt steht. Stattdessen setzt Elixir auf Recursion. Um Loops umzusetzen, können Sie bei Elixir deshalb auf funktionale Operatoren wie Enum.each und Enum.map zurückgreifen. Comprehensions Eine Möglichkeit, einen for-Loop zu simulieren, bieten Comprehensions (die „echten“ for-Loops zum Verwechseln ähnlich sehen): for x Die Elixir-Dokumentation hält weitere Beispiele und Informationen zu diesem Thema bereit. Pipe-Operator Elixirs Pipe-Operator liefert Ihnen eine saubere Syntax, um Funktionsergebnisse miteinander zu verknüpfen. Das könnte man auch als eine elegantere Form von Funktions-Nesting bezeichnen. Ein simples Pipe-Operator-Beispiel für unsere books_and_lengths-Collection: books_and_lengths |> Map.keys() |> Enum.map(&String.upcase/1) |> Enum.join(", ") |> IO.puts() Der Output sieht folgendermaßen aus: The Bhagavad Gita, Tao Te Ching Concurrency in Elixir Eine weitere Stärke von Elixir ist Concurrency: In der Programmiersprache kommen sogenannte „Actors“ zum Einsatz, die so etwas wie virtuelle Threads darstellen (weil sie keine vollständigen Betriebssystemprozesse sind) und über „Message Passing“ eine vereinfachte, gleichzeitige Kommunikation ermöglichen. Folgendes Code-Beispiel entstammt der Elixir-Dokumentation und veranschaulicht Message Handling: defmodule Example do def listen do receive do {:ok, "hello"} -> IO.puts("World") end listen() end end Mit Blick auf dieses Code-Beispiel ist zu beachten, dass die listen-Funktion rekursiv ist – sie ruft sich am Ende selbst auf. Ohne sie würde der Prozess beendet. Um diesen Actor zu starten, nutzen wir spawn: pid = spawn(Example, :listen, []) Anschließend können wir eine Nachricht aus dem Hauptprozess senden, indem wir die gespeicherte pid verwenden: send pid, {:ok, "hello"} Das sorgt für den Konsolen-Output „World“. (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!