article

Funktionale Programmierung ist ein Programmierparadigma. Programme bestehen hier ausschliesslich aus einer Vielzahl von Funktionen, daher der Name. Das Hauptprogramm ist eine Funktion, welche die Eingabedaten als Argument erhält und die Ausgabedaten als seinen Wert zurückliefert. Diese Hauptfunktion verwendet in Ihrer Definition üblicherweise weitere Funktionen, die wiederum ihrerseits weitere Funktionen verwenden, und das geht so weiter, bis irgendwann, am Boden der Aufrufhierarchie ankommend, nur noch die Grundfunktionen der Programmiersprache verwendet werden, z.B. Addition, Konkatenation etc.

Beginnen wir mit dem Begriff der Funktion. Der Funktionsbegriff, der einem bei den meisten Programmiersprachen begegnet (siehe Funktion (Programmierung)), weicht vom Funktionsbegriff der Mathematik (siehe Funktion (Mathematik)) ab. Z.B findet man in vielen Programmiersprachen die folgende nützliche Funktion function random() { float x = /* Zufallszahl wird vom System abgefragt */ return x } welche bei jedem Aufruf eine neue Zufallszahl zwischen 0 und 1 zurück liefert. Dies ist nun genau ein Verhalten (bei jedem Aufruf wird ein anderer Wert zurückgeliefert, man spricht von einem Seiteneffekt), welches man bei mathematischen Funktionen nicht antrifft! Eine mathematische Funktion liefert für die gleichen Eingabewerte (Argumente) immer den gleichen Ausgabewert zurück. In der Regel sind Seiteneffekte unerwünscht, weil sie dem Programmierer die Übersicht und die Fehlersuche bzw. Korrektheitsprüfung erschweren.

Die Funktionen funktionaler Programmiersprachen verhalten sich hingegen wie mathematische Funktionen. Dies wird u.a. erreicht, indem eine Zuweisung der Form x = x + 1 ausgeschlossen wird.

Mathematisch gelesen ergibt diese Zeile auch wenig Sinn, denn das wäre eine Gleichung und es käme

x = x + 1 \Leftrightarrow 0 = 1
raus, was unwahr ist. Gemeint ist diese Zuweisung ja als
x := x + 1,
wobei das x auf der linken Seite ein anderes x ist, als das auf der rechten Seite! Das ist zwar für die Programmierung nützlich, entspricht aber nicht dem Verhalten von Variablen in der Mathematik, wo eine Variable x, wenn sie einen Wert zugewiesen bekommen hat, genau diesen Wert immer behält und nicht mal den einen und mal den anderen Wert im Verlauf einer Rechnung annimmt (Referentielle Transparenz).

Ein Ansatz der funktionalen Programmierung ist, dass ihre Funktionen sich mehr wie mathematische Funktionen verhalten, damit man die Rechen- und Beweismethoden der Mathematik besser auf Programme anwenden kann (um vor allem ihre Korrektheit zu beweisen).

Sie vertritt weiter die Auffassung, das Funktionen ein geeignetes Mittel zur Modularisierung von Programmen darstellen, indem die Hauptfunktionen aus einer Vielzahl anderer Funktionen zusammengesetzt (komponiert) werden. Hierfür bieten funktionale Programmiersprachen verstärkte Unterstützung, z.B. verhalten sich Funktionen weitestgehend wie andere Datentypen, können als Argumente und Rückgabewerte verwendet werden (Funktionen höherer Ordnung).

Der Programmierer legt fest, wie die Funktionen zusammengesetzt sind, es gibt jedoch noch Freiheiten in der Auswertung, die von Übersetzern zu Optimierungszwecken genutzt werden können.

Abgrenzung von Imperativer Programmierung


Im Gegensatz zu imperativen Programmen die aus Rechenanweisungen bestehen, sind Funktionale Programme eine Menge von (Funktions)-Definitionen, die man mathematisch als partielle Abbildungen von Eingabedaten auf Ausgabedaten auffassen kann.

Ein typisches Beispiel ist die Berechnung der Fakultät n! einer Zahl n (n! = 1 ∙ 2 ∙ 3 ∙ ... ∙ n), hier eine imperative Lösung:

Eingabe: Zahl n
Ausgabe: Zahl b (=1 ∙ 2 ∙ 3 ∙ ... ∙ n)
Algorithmus:
b ⟵ 1 (Zuweisung)
solange n ≠ 0 führe aus:
b ⟵ n ∙ b
n ⟵ n − 1
Ausgabe: b

Charakteristisch für die Imperative Programmierung sind hier die Zuweisungen (Änderung von Werten, durch das Zeichen ⟵ im Pseudocode repräsentiert). Zwar berechnet der Algorithmus die Fakultät der Zahl n, aber die Korrektheit dieses Rechenweges ist nicht offensichtlich. (Siehe auch: Programmverifikation)

Nun kann man die Fakultät jedoch auch mithilfe von rekursiven Funktionen definieren, was zur funktionalen Lösung führt. n! = f(n) = \begin{cases} n \cdot f(n-1) &\mathrm{f\ddot ur}\ \ n > 1\\ 1 &\mathrm{f\ddot ur}\ \ n = 1\end{cases}

Funktion f(n):
wenn n > 1 dann:
Ausgabe: n∙f(n-1)
sonst wenn n = 1 dann:
Ausgabe: 1

Die Funktionale Programmierung kommt also ohne Schleifen und Zuweisungen aus.

Theoretische Grundlagen


Als theoretische Grundlage dient das λ-Kalkül (Lambda-Kalkül) von Alonzo Church. Jeder Ausdruck wird dabei als auswertbare Funktion betrachtet, so dass Funktionen als Parameter übergeben werden können.

Das Lambda-Kalkül erlaubt es vollständige Teilausdrücke separat auszuwerten. Dies ist der wichtigste Vorteil gegenüber der imperativen Programmierung. Dieses Konzept vereinfacht die Programmverifikation und Programmoptimierung, beispielsweise die Überführung der Programme in eine parallel auswertbare Form.

Typsystem


Historisch ist LISP als die erste funktionale Programmiersprache aufzufassen; Sprachen der LISP-Familie (wie auch Scheme) sind dynamisch getypt. Seit der Entwicklung von Standard-ML (SML) konzentriert sich die Forschung auf dem Gebiet der funktionalen Programmiersprachen jedoch auf statisch getypte Sprachen, insbesondere auf solche, die das Typsystem nach Hindley und Milner verwenden. Der Vorteil dieses Typsystems ist die Verfügbarkeit von parametrischem Polymorphismus zusammen mit Typinferenz: Programmierer müssen die Typen ihrer Funktionen und anderen Werte nicht angeben, sondern bekommen sie gratis vom Übersetzer ausgerechnet, der zugleich noch während der Übersetzung Typfehler monieren kann. Dies wird allgemein als wesentlicher Vorteil gegenüber dynamisch getypten Sprachen (LISP, Python) aufgefasst, die zwar ebenfalls keine Typannotationen benötigen (im Gegensatz z.B. zu Java oder C), dafür aber Typfehler erst zur Laufzeit anmahnen können (wenn überhaupt).

Das Hindley-Milner-System erlaubt allerdings nur Polymorphismus ersten Ranges; Erweiterungen für Polymorphismus zweiten und allgemein k-ten Ranges sind inzwischen in dem Haskell-Übersetzer GHC als Erweiterungen verfügbar, bedingen jedoch wieder explizite Annotationen (Typinferenz ab Polymorphismus zweiten Ranges ist unentscheidbar).

Rein funktionale Programmiersprachen


Rein (engl. pure) funktionale Programmiersprachen fassen Programme als mathematische Funktion auf: Ein Ausdruck hat dort während der Programmausführung immer den gleichen Wert. Es gibt keine Zustandsvariablen, die während einer Berechnung geändert werden. Um erwünschte Wirkungen oder Seiteneffekte (Benutzerinteraktion, Eingabe/Ausgabe-Operationen) beschreiben zu können, sind meist besondere Vorkehrungen notwendig. Die meisten funktionalen Programmiersprachen (SML, CAML) erlauben allerdings solche Wirkungen und sind daher keine reinen funktionalen Programmiersprachen.

Um Programmierung auch mit Wirkungen zu erlauben, ohne dabei zu einer unreinen (engl. impure) Sprache zu werden, wurde bei der Entwicklung der Sprache Haskell das aus der Kategorientheorie stammende Konzept der Monaden entwickelt (insbesondere von Eugenio Moggi und Philip Wadler), welches Wirkbehaftung durch Parametrische Typen ausdrückt und somit das Typsystem dazu zwingt, zwischen effektbehafteten und effektfreien Ausdrücken zu unterscheiden. Dieses Konzept wird auch in Clean verwendet.

Funktionen höherer Ordnung


Man unterscheidet Funktionen erster Ordnung und Funktionen höherer Ordnung. Bei Funktionen höherer Ordnung sind Funktionen selbst Werte. Dies erlaubt es insbesondere, Funktionen als Ergebnisse oder Parameter anderer Funktionen zu verwenden. Ein typisches Beispiel ist der Ableitungsoperator: Eingabe ist eine differenzierbare Funktion, Ausgabe ist die Ableitung dieser Funktion. Ein Beispiel aus der Informatik ist map, um eine beliebige, übergebene Funktion f auf jedes Element einer Liste anzuwenden. Definition in Haskell:

map f = [ map f (x:xs) = f x : map f xs

In der ersten Zeile wird das Ergebnis für eine leere Liste * zurückgegeben; die zweite Zeile wendet die Funktion f auf das erste Listenelement x an und führt dann einen Rekursionsschritt für die restliche Liste xs durch.

Bedarfsauswertung und strenge Auswertung


Funktionale Sprachen kann man auch nach ihrer Auswertungsstrategie unterscheiden: Bei strenger Auswertung (engl. eager bzw. strict evaluation) werden die Argumente von Funktionen zuerst ausgewertet. Dagegen werden bei der nicht-strikten Auswertung zunächst die Ausdrücke als ganzes übergeben und dann ausgewertet.

Man könnte zum Beispiel (3+5)^2 auf zwei Arten berechnen, wobei die Gleichung im ersten Fall strikt ausgewertet wird, da erst die Argumente der Potenz-Funktion berechnet werden. Im zweiten Fall werden diese Argumente erst bei Bedarf ausgewertet, also nachdem die Potenzfunktion aufgelöst wurde:

  • (3+5)^2 = 8^2 = 8 \cdot 8 = 64
  • (3+5)^2 = (3+5)\cdot(3+5) = 8 \cdot 8 = 64

Eine Variante der nicht-strikten Auswertung ist die Bedarfsauswertung (engl. lazy evaluation), bei der Ausdrücke erst ausgewertet werden, wenn deren Wert in einer Berechnung benötigt wird. Dadurch lassen sich z.B. unendlich große Datenstrukturen (die Liste aller natürlicher Zahlen, die Liste aller Primzahlen, etc.) definieren und bestimmte Algorithmen vereinfachen sich.

Manche Berechnungen lassen sich mit strenger Auswertung, andere mit Bedarfsauswertung effizienter ausführen. Terminiert die strenge Auswertung eines Ausdruckes, so terminiert auch die nicht-strikte Auswertung.

Funktionale Algorithmen und Datenstrukturen


Algorithmen geben vorteilhafte Verfahren für die Lösung wichtiger Probleme an (z.B. Sortieren) und sind in der Regel gut analysiert, so dass ein Entwickler auf bewährte, einschätzbare Lösungen zurückgreifen kann. Gleiches leisten Datenstrukturen für die Organisation von Daten. Sammlungen von guten Algorithmen und Datenstrukturen haben somit eine große praktische Bedeutung.

Der Verzicht auf zerstörerische Zuweisungen führt dazu, dass etliche klassische Algorithmen und Datenstrukturen, die regen Gebrauch von dieser Möglichkeit machen, so nicht für funktionale Sprachen verwendet werden können und man nach neuen Lösungen suchen muss.

Chris Okasaki schreibt: Auch wenn die meisten dieser Bücher Datenstrukturen behaupten, das sie unabhängig von einer bestimmten Programmiersprache sind, so sind sie leider nur sprachunabhängig im Sinne Henry Fords: Programmierer können jede Programmiersprache benutzen, solange sie imperativ ist.

Gerade rein funktionale Datenstrukturen sind von ihrer Natur her anders, als die gewohnten Datenstrukturen, die meist nur eine Version ihrer Daten verwalten (ephemerale Datenstrukturen), wohingegen funktionale Datenstrukturen mehrere Versionen verwalten (persistente Datenstrukturen).

Nebenläufigkeit und Verteilung


Eine weitere Einteilung kann danach vorgenommen werden, ob die Sprache für sequentielle oder nebenläufige Ausführung entworfen wurde und ob es Unterstützung für die Verteilung auf verschiedene Rechnerknoten gibt. Ein Beispiel für eine nebenläufige, verteilte funktionale Sprache ist Erlang.

Beispiele


Folgende Programme definieren eine Funktion ringarea, die die Fläche berechnet, die zwischen den beiden konzentrischen Kreisen mit den Radien R und r liegt. Dazu werden die Konstante pi und die Hilfsfunktion sq definiert. Diese werden von ringarea dann für die Berechnung benutzt.

SML

local val pi = 3.14 val sq = fn (x:real) => x*x in fun ringarea(R, r) = pi*(sq R - sq r) end

Der Typ von x muß hier explizit angegeben werden, da ein SML97-Übersetzer sonst den Typ int inferieren würde. Das zugrundeliegende Problem ist das der Überladung von Operatoren; dieses Problem wurde erst mit Einführung von Typklassen gelöst, allerdings in Haskell und verwandten Sprachen.

Joy

DEFINE pi == 3.14; sq == dup *; ringarea == sq swap sq swap - pi *. Man beachte, dass hier alle Variablen Funktionen bezeichnen (auch pi ist eine Funktion!).

Literatur


  • Okasaki, Chris: Purely Functional Data Structures, Cambridge University Press 1999, ISBN 0521663504
  • Pepper, Peter und Petra Hofstedt: Funktionale Programmierung, Springer 2006, ISBN 3-540-20959-X
  • Rabhi, Fethi und Guy Lapalme: Algorithms - A Functional Programming Approach, Addison Wesley 1999, ISBN 0201596040

Siehe auch


Weblinks


Funktionale Programmiersprache | Programmierparadigma

Funkcionalno programiranje | Funkcionální programování | Functional programming | Programación funcional | Funktionaalinen ohjelmointikieli | Programmation fonctionnelle | תכנות פונקציונלי | 関数型言語 | 함수형 프로그래밍 | Functionele programmeertaal | Programowanie funkcyjne | Programação funcional | Функциональное программирование | Funkcionálne programovanie | Funktionell programmering | 函數式編程

 

This article is licensed under the GNU Free Documentation License. It uses material from the "Funktionale Programmierung".

Home Pageartsbusinesscomputersgameshealthhospitalshomekids & teensnewsphysiciansrecreationreferenceregionalscienceshoppingsocietysportsworld