Aus Linux-Magazin 04/2022

Funktionale Programmierung am Beispiel von Elixir

© Catalina-Gabriela Molnar / 123RF.com

In Elixir spielen Funktionen die Hauptrolle. Das hat handfeste Vorteile wie einfache verteilte Ausführung und ausgezeichnete Testbarkeit.

Während objektorientierte Programmiersprachen Methoden benutzen, um Code zu strukturieren, erzielen funktionale Programmiersprachen dasselbe mit Funktionen. Hört man dieses Statement als Neuling in der funktionalen Programmierung, kommt einem vielleicht in den Sinn: Wo soll denn der Unterschied zwischen Methoden und Funktionen liegen?

Der Unterschied besteht darin, dass man eine Funktion von außen nicht beeinflussen kann. Definiert man in der funktionalen Programmiersprache Elixir [1] die Funktion »beispiel/1« (das »/1«, die sogenannte Arity, gibt die Anzahl der möglichen Argumente an), dann liefert der Funktionsaufruf »beispiel(10)« immer dasselbe Ergebnis, völlig egal, was im Programm außerhalb der Funktion sonst noch geschieht.

Bei objektorientierten Programmiersprachen sieht das anders aus. Dort kann man von außen Einfluss auf das Ergebnis einer Methode nehmen. Man braucht hier nur an Variablen zu denken: In objektorientierten Programmiersprachen lassen sie sich außerhalb der Methode verändern, mit direktem Einfluss auf deren Resultat. In Elixir liefert eine pure Funktion beim Aufruf mit denselben Argumenten immer dasselbe Ergebnis und verursacht keine Seiteneffekte.

Concurrency

Warum sind fehlende Seiteneffekten nun so eine große Sache? Schließlich haben Programmierer von OO-Sprachen durch Seiteneffekte auch keine schlaflosen Nächte.

Ein großer Vorteil von puren Funktionen liegt darin, dass man eine einzelne Funktion auf einer beliebigen CPU ausführen kann. Mit Elixir lassen sich sogar recht einfach mehrere Server eines Netzwerks oder Clusters zusammenfassen, sodass Funktionen nicht nur innerhalb aller CPUs eines Nodes ausgeführt werden, sondern auch auf anderen Nodes. Zur Ausführung genügen die Funktion und ihre Argumente.

Während sich Ruby-Programmierer über Concurrency und parallele Abläufe viele Gedanken machen müssen und am Ende doch oft scheitern, können Elixir-Programmierer über Nebenläufigkeit nur müde lächeln. Die einzelnen Funktionen eines Elixir-Programms lassen sich ohne weitere Umstände parallel in einem Cluster abarbeiten.

Paradigmen

Diese Vorteile realisiert Elixir durch einige für andere Programmiersprachen ungewöhnliche Programmierparadigmen. Als Umsteiger wird man als Erstes mit der Idee der immutablen (unveränderlichen) Variablen konfrontiert. Der einer Variablen zugewiesene Speicherbereich lässt sich in Elixir nicht überschreiben. Zwar darf man der Variablen einen neuen Wert zuweisen, doch der überschreibt den alten Speicherbereich nicht, sondern landet in einem neuen.

Das hört sich zunächst einmal wahnsinnig ressourcenfressend an. Setzt man sich aber ein wenig mit der Arbeitsweise von CPUs und Speicher auseinander und weiß, dass es sich bei Funktionen um eher kleine Einheiten von oft weniger als 25 Zeilen Code handelt, dann wird schnell klar: Es ist nicht weiter tragisch, wenn die Funktion ein wenig mehr Speicher verbraucht, dafür aber schneller läuft. Einen Speicherbereich zu verändern erfordert wesentlich mehr CPU-Zyklen, als ihn neu zu beschreiben. Am Ende der Funktion wird einfach alles wieder freigegeben. Was verschwenderisch aussieht, ist tatsächlich wesentlich schneller.

Ein weiteres für den Umsteiger erst einmal gewöhnungsbedürftiges Thema sind die Scopes von Variablen. In Elixir kann man nicht mehr von überall auf fast alles zugreifen. OK, das klappt auch in Ruby [2] nur begrenzt, aber dort lassen sich etwa oft benutzte Instanzvariablen (deren Name fängt mit einem Klammeraffen an) innerhalb des gesamten Codes einer Instanz eines Objekts lesen und schreiben. Elixir gibt sich hier viel kleinteiliger: Ein Scope bewegt sich immer innerhalb einer Schachtelstruktur, ähnlich wie bei einer russischen Matrjoschka.

Rekursion

Als letzte direkt auffällige Besonderheit muss man die Rekursion ansprechen. In anderen Programmiersprachen oft eher als Problemfeld angesehen, ist sie in der funktionalen Welt ein ganz normales Werkzeug, ohne das sich die meisten Probleme gar nicht lösen lassen. Ein einfacher Countdown sieht dann etwa so aus wie in Listing 1.

Elixir sucht von oben ausgehend die für den Aufruf passende Funktion. Es gibt zwei Funktionen mit der Arity 1, von denen die erste aber nur beim Argument 1 und die zweite nur bei Integern größer als 0 greift. Das stellt sicher, dass negative Zahlen das Programm nicht ins Nirwana befördern.

Listing 1

Countdown

defmodule Beispiel do
  def countdown(1) do
    IO.puts(1)
  end
  def countdown(x) when is_integer(x) and x > 0 do
    IO.puts(x)
    countdown(x - 1)
  end
end

Fazit

Viele Argumente sprechen für funktionale Programmiersprachen. Es gibt allerdings auch viele unterschiedliche funktionale Sprachen, und nicht alle eignen sich ideal für den Ein- oder Umstieg. Elixir bietet mit seiner leicht lesbaren und an Ruby angelehnten Syntax sicherlich einen guten Einstieg.

Zum Schluss noch mein Lieblingsargument für Elixir: die extrem gute Testbarkeit. Eine einzelne Funktion ist nicht nur für den Menschen gut lesbar und leicht verständlich, sie eignet sich auch sehr gut für automatische Tests. Da es keine Seiteneffekte gibt, kann man sich auf das Ergebnis verlassen. (jcb/jlu)

Infos

  1. Elixir: Stefan Wintermeyer, “Neue Wege”, LM 07/2021, S. 29, https://www.lm-online.de/46209
  2. Ruby: André Ben Hamou, “Admin-Juwel”, LM 06/2012, S. 24, https://www.lm-online.de/25873
DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 2 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben