
Dieser Artikel erklärt die Motivation für die Entwicklung funktionaler Sprachen, geht auf die konkreten Probleme vieler aktuell verbreiteter Sprachen ein, beschreibt Lösungsansätze funktionaler Programmierung und liefert Gründe für deren praktische Verwendung in allen Bereichen.
Abstract
Die Probleme vieler moderner Programmiersprachen sind dieselben, wie die Probleme der ersten Hochsprachen. Das liegt daran, dass sich das Grundkonzept dieser Sprachen trotz eines gestiegenen Abstraktionsgrades nicht geändert hat.
Funktionale Programmiersprachen wurden entwickelt, um diese spätestens seit Ende der 1970er Jahre bekannten Probleme zu lösen. Das Programmierparadigma führt zu weniger komplexen Programmen, lesbarem, verständlichen Code und effizienter Entwicklung. Weil die Lösung dieser Probleme erst durch die Entfernung von der Vorgangsweise des Prozessors möglich wird, ist es notwendig, Kontrolle über maschinennahe Vorgänge an den Compiler abzugeben. Das erschwert bestimmte Performanceoptimierungen und macht die Verwendung von Garbage Collection notwendig.
Alte Probleme moderner Sprachen
Als John Backus in seiner Turing Award Lecture 1977 von der Notwendigkeit eines neuen Programmierparadigmas sprach, kritisierte er unter anderem die folgenden Probleme klassischer (imperativer) Sprachen:
- Sprachen werden immer komplexer, aber kaum ausdrucksstärker
- Semantische Aussagen sind eng mit internen Zustandsänderungen verbunden
- Programme haben keine nützlichen mathematischen Eigenschaften
- Es fehlen Mechanismen, um Programme effizient kombinieren zu können
Den Grund für diese Probleme sah Backus vor allem in der starken Anlehnung klassischer Sprachen an die Konzepte der Von-Neumann-Prozessorarchitektur — er nennt diese Sprachen deshalb Von-Neumann-Sprachen. Backus vertrat die Meinung, dass es zu enormen Vorteilen führen würde, eine Sprache zu entwickeln, die sich stärker von der Funktionsweise des Prozessors abgrenzt.
Moderne imperative oder objektorientierte Sprachen lösen die von Backus kritisierten Probleme nicht. Sie verbessern bestimmte Aspekte existierender Sprachen, ohne sich diesen grundsätzlichen Problemen zu stellen.
Wieso ist eine stärkere Abgrenzung von der Von-Neumann-Architektur erstrebenswert?
Ein Bericht der Denkfabrik RAND, die 1987 im Rahmen eines Forschungsprojektes für die DARPA durchgeführt wurde, untersucht die Ursache für die Schwierigkeiten der Programmierung in Von-Neumann-Sprachen.
“Often the assertion is made that programs in von Neumann languages like Fortran, Pascal or C are difficult so synthesize, analyze, modify or extend. While the assertion appears to be correct, usual arguments given in its support are not very convincing.”
Der Bericht kommt zum Schluss, dass Von-Neumann-Sprachen die Programmierung schwierig machen, weil sie keine Aussagen der Form “Berechne jede der Funktionen f1(x), …, fn(x) für dasselbe x” zulassen. Der Grund dafür ist, dass in Von-Neunmann-Sprachen ein beliebiges Register des Prozessors (bzw. ein Bereich im Speicher oder eine Variable) jederzeit überschrieben werden kann. Es kann also nicht garantiert werden, dass die Argumente einer Funktion nicht durch die Anwendung der Funktion verändert werden.
Das Hauptproblem von klassischen Programmiersprachen sind also veränderbare Variablen und veränderbare Funktionsargumente, die es unmöglich machen, Funktionen so zu behandeln, wie es mit mathematischen Funktionen intuitiv sinnvoll ist. Es ist nicht garantiert, dass das Ergebnis derselben Funktion, mit denselben Argumenten bei mehrmaliger Ausführung dasselbe Ergebnis zurückgibt.
Diese Eigenschaft macht Programme in klassischen Sprachen komplex und schwer zu verstehen und führt zu den von Backus zuvor beschriebenen Problemen. Das gilt heute genauso für moderne imperative Sprachen, wie zum Zeitpunkt des Berichtes von RAND für Fortran, Pascal oder C.
Ein funktionales Programm ist eine Sequenz von Funktionsapplikationen
Funktionale Sprachen lösen genau dieses Problem der Unvorhersehbarkeit von Funktionen. Sie erzwingen Funktionen ohne Seiteneffekte, erlauben keine Veränderung von Funktionsargumenten und Variablen und ermöglichen so ein zuverlässiges, vorhersehbares Verhalten von Funktionen.
Alle zentralen Vorteile funktionaler Sprachen ergeben sich direkt oder indirekt aus diesem Konzept. Es ist deshalb von zentraler Bedeutung, die direkten Auswirkungen davon zu verstehen. Das Ergebnis einer Funktion ist durch ihre Argumente immer eindeutig bestimmt — das Ergebnis ist nicht vom Wert irgendeiner Variable (weil es keine veränderbaren Variablen gibt) abhängig. Das führt direkt zu einer Reihe von Vorteilen:
- Einfachheit und Verständlichkeit: Der Ablauf eines funktionalen Programmes ist sehr linear und dadurch intuitiv leichter verständlich. Man kann sich den Ablauf eines funktionalen Programmes einfach als den Fluss von Daten durch Funktionen vorstellen.
- Gratis loose Coupling: Solange die Funktionssignatur sich nicht ändert, kann eine Funktion beliebig verändert werden, ohne dass dadurch außerhalb der Funktion Probleme entstehen können. Eine Funktion kann immer durch eine beliebige Funktion mit der gleichen Signatur ersetzt werden. Diese Eigenschaft macht nicht nur Refactoring unglaublich einfach, sondern führt auch zu einer sehr hohen Wiederverwendbarkeit von Funktionen.
- Kombinierbarkeit: Wie in der Mathematik entsteht durch die Hintereinanderausführung von Funktionen eine neue Funktion. Dieses Konzept ist ebenso einfach wie mächtig.
Probleme
Durch den höheren Abstraktionsgrad funktionaler Sprachen ist es notwendig, Kontrolle über maschinennahe Vorgänge an den Compiler abzugeben. Das erschwert bestimmte Performanceoptimierungen und macht in der Regel die Verwendung von Garbage Collection notwendig.
Zusammengefasst
Funktionale Programmierung löst viele Probleme klassischer Sprachen und macht Softwareentwicklung produktiver. Mögliche Performancenachteile sind für die meisten Anwendungsgebiete nicht relevant und werden durch die effiziente Ausnützung von mehreren Prozessorkernen weiter ausgeglichen. Der letzte entscheidende Schritt in Richtung produktiver Programmierung wurde in den 1950er Jahren mit der Entwicklung von FORTRAN und der Verbreitung der ersten Hochsprachen getan. Probleme dieser Sprachen wurden — vor allem von FORTRANS Hauptentwickler — bald erkannt und führten zur Entwicklung der funktionalen Programmierung. Wegen damals schlechterer Performance konnte sich dieses Paradigma nicht flächendeckend durchsetzen. Dass die meisten heute etablierten Sprachen nach wie vor von FORTRAN
abstammen, hat also vor allem historische Gründe. Mit FORTRANS Paradigma übernehmen moderne imperative Sprachen auch dessen grundlegende Probleme, die nicht durch inkrementelle Verbesserungen behoben werden können. Der nächste Schritt in Richtung produktiver Programmierung ist die flächendeckende Verbreitung von funktionaler Programmierung. Für Anwendungsgebiete, in der bereits heute Sprachen mit Garbage-Collection verwendet werden, bringt die Verwendung funktionaler Sprachen enorme Vorteile bei praktisch keinen Nachteilen.
Klassische Programmiersprachen: “Wie können wir Assembler (dann Fortran, dann C usw.) verbessern?”
Funktionale Programmierung: “Wie würde eine Sprache aussehen, in der Programmierung einfach ist?”
Wir setzen auf F#
Deshalb setzen wir bei allen unseren Projekten auf funktionale Programmierung. Die Sprache unserer Wahl ist dabei — aus vielen Gründen — F#.
