Funktionale Programmierung ist einProgrammierparadigma, in demFunktionen nicht nur definiert und angewendet werden können, sondern auch wie Daten miteinander verknüpft, als Parameter verwendet und als Funktionsergebnisse auftreten können. Dadurch kann ein funktionales Programm sehr weitreichend neue Berechnungsvorschriften zur Laufzeit zusammensetzen und anwenden.Programmiersprachen, die funktionale Programmierung ermöglichen, werden als funktionale Programmiersprachen bezeichnet.
Die funktionale Programmierung entspringt der mathematischen Grundlagenforschung. In den 1930er Jahren entwickelteAlonzo Church denLambda-Kalkül als Instrument, um das Entscheidungsproblem zu bearbeiten und dazu den Begriff der berechenbaren Funktion zu definieren. Der Lambda-Kalkül selbst beschäftigt sich nicht mit bestimmten Funktionen, sondern ist nur ein Regelwerk dafür, wie die Anwendung von Funktionen auf ihre Argumente erfolgt und wie dabei mit freien und gebundenen Variablen verfahren wird.
Die besonderen Eigenschaften der funktionalen Programmierung ermöglichen es, auf die in derprozeduralen Programmierung benötigten Zustandsänderungen außerhalb eines Unterprogrammes, die auchSeiteneffekte genannt werden, zu verzichten, falls sie überhaupt möglich sind. Der Verzicht auf die Seiteneffekte vereinfacht auf der einen Seite die semantische Analyse eines Computerprogramms erheblich und eröffnet auf der anderen Seite weitreichende Möglichkeiten zur regelbasierten, algebraischen Programmtransformation und -synthese. Daraus ergibt sich die erhebliche praktische Bedeutung der funktionalen Programmierung für die Informatik.
Eine weitere Konsequenz ist, dass es in funktionaler Programmierung besonders einfach ist, Algorithmen ohne Berücksichtigung der Beschaffenheit der bearbeiteten Datenobjekte zu beschreiben und dadurch generischen Programmcode zu erstellen. Viele funktionale Verfahren sind so generisch, dass sie seit den 1950er Jahren keiner Anpassung unterworfen werden mussten.
Die erste bedeutende, in funktionaler Programmierung erstellte Software ist der vonJohn McCarthy im Jahr 1958 erstellte, metazirkuläreLisp-Interpreter mit Nameneval, der die Semantik von LISP aus fünf einfachen Funktionen zusammengesetzt darstellt. Er findet auf einer DIN-A4-Seite Platz und ist bis heute konzeptionelle Grundlage für die Implementierung der meisten Programmiersprachen.
Die funktionale Programmierung ist durch folgende Eigenschaften gekennzeichnet:
- Computerprogramme werden als Funktionen verstanden, die für eine Eingabe eine Ausgabe liefern, die nur von dieser abhängig ist.
- Funktionen werden nicht als Abfolge von Anweisungen dargestellt, sondern als ineinander verschachtelte Funktionsaufrufe.
- Funktionen sind gegenüber allen anderen Datenobjekten gleichberechtigt. Das bedeutet, dass sie als Parameter in Funktionen eingehen dürfen und ebenso als Berechnungsergebnisse aus Funktionen hervorgehen können. Insbesondere können Funktionen wie andere Datenobjekte zur Laufzeit erstellt werden oder entfallen.
- Eine Funktion kann auf Variablen Bezug nehmen, die dem Kontext angehören, in dem ihre Erstellung erfolgt ist. Dies kann sie auch dann noch tun, wenn sie den Kontext verlassen hat. Die Belegung der Variablen zum Zeitpunkt des Verlassens dieses Kontextes wird dann innerhalb dieser Funktion eingefroren. Eine so entstandene Funktion heißt Closure und die eingefrorenen Variablenbelegungen heißen Closure-Variablen.
- Funktionsdefinitionen können insbesondere bei Funktionsanwendungen ohne explizite Namensgebung literal in der Stellung des Funktionssymbols stehen. Solche Funktionen heißen anonym und werden oft salopp „Lambdas“ genannt.
Einerein funktionale Programmiersprache wäre striktisomorph zumLambda-Kalkül. Das gilt mit minimalen Einschränkungen beispielsweise für dieesoterische Programmierspracheunlambda.
Wichtige Vertreter funktionaler Programmiersprachen sind die Lisp-Programmiersprachenfamilie und die rein funktionale Programmiersprache Haskell. Vornehmlich zur funktionalen Programmierung gedachte Sprachen sind:
Fast alle in jüngster Zeit entstandenen Programmiersprachen gestatten funktionale Programmierung. Sprachen, die funktionale Programmierung neben anderen Paradigmen ermöglichen, sind zum Beispiel Perl, ECMAScript, Dylan, Ruby und Visual Basic.NET. Python hat Einschränkungen bei der Formulierung von anonymen Funktionen. Viele eigentlich objektorientierte Sprachen wurden in jüngsten Versionen aufgerüstet wie C++ (ab Version 11),Delphi (ab Version 2009) oder Java (ab Version 8). Allerdings leidet darunter die Syntax, sodass die Kompaktheit von LISP oder Haskell nicht erreicht wird.
Populäre Programmiersprachen, die keinerlei Möglichkeiten zur funktionalen Programmierung bieten, sind zum Beispiel C und VBA.
Die allgemeingültigen Konzepte der funktionalen Programmierung entstammen der Mathematik der 1930er und 1940er Jahre. Von besonderer Bedeutung sind derLambda-Kalkül und dieKategorientheorie. Der Lambda-Kalkül ist ein operatives Regelwerk, mit dessen Hilfe die Bedeutung von symbolischen Ausdrücken bestimmt werden kann. Kategorientheoretisch (Kategorientheorie) sind Datentypen Objekte und Funktionen Morphismen, die zwischen diesen abbilden und für die die gewöhnliche Funktionskomposition als zweistellige, assoziative Verknüpfung und die Identitäts-Abbildung als neutrales Element definiert ist. Damit bilden die Funktionen eine „Gruppe ohne das Gesetz vom inversen Element“.
In der imperativen Programmierung übliche Datenstrukturen wie Arrays sind in der funktionalen Programmierung schwierig im Gebrauch, da sie durch Rekursion nur schwierig darstellbar sind, auch wenn es Ansätze wie dasFunctional Programming System gibt, die explizit mit solchen Strukturen arbeiten. Listen und Bäume sind hingegen sehr gut mit der Funktionalen Programmierung verträglich.
Sei
ein beliebiger Datentyp. Dann ist der Typ
der beliebig langen Listen mit mindestens 0 Elementen des Typs
gegeben durch die Rekursion
. Dabei ist
die leere Liste und
eine zweistellige Operation, die aus einem Wert
und einer Liste
eine neue Liste
konstruiert, indem sie
vorne an
anhängt.
Man kann diesen rekursiven Aufbau benutzen, um Funktionen
zu schreiben, die eine Liste zerlegen und dabei einen Wert ermitteln. Eine solche Funktion
heißt Katamorphismus.
Sei
ein Datentyp,
ein Wert und
eine Abbildung. Dann ist durch

der Katamorphismus (zu griech.κατά = entlang, herab) mit Initialwert
und Verknüpfung
gegeben. In der üblichen Notation mit Bananenklammern wird er als
geschrieben. Im Sinne der funktionalen Programmierung ist die Zirkumfix-Operation
eine Funktion höherer Ordnung. In funktionalen Programmiersprachen gibt es zur Berechnung von Katamorphismen die Funktionreduce oderfold. Ein Katamorphismus, der obiger Definition folgt, ist rechtshändig. Er entspricht der folgenden Programmschleife, die eine Liste von ihrem Ende her traversiert:

Ein linkshändiger Katamorphismus würde beim Index 1 beginnen.
Viele grundlegende Verfahren der Programmierung sind Katamorphismen. So berechnet
die Summe einer Zahlenliste,
hängt Strings aneinander und
mit
berechnet die Länge einer Liste.Eine Funktion, die eine Liste
nach Elementen filtert, die die Eigenschaft
erfüllen, kann mit der Funktion
aus
errechnet werden, die wie folgt definiert ist:
mit
falls
und
sonst.
Ist die Operation
assoziativ mit dem neutralen Element
, dann ist der Katamorphismus
die eindeutige Erweiterung der Operation
auf beliebig viele Argumente. Das ist eine nützliche Eigenschaft, die viel Arbeit in der praktischen Programmierung einsparen kann. Ist zum Beispiel eine zweistellige Funktions-Komposition
bereits implementiert, so lässt sich die Komposition
von
Funktionen als
darstellen (dabei ist
die identische Abbildung).
Während Katamorphismen Listen zu Einzelwerten reduzieren, bauen Anamorphismen Listen aus Einzelwerten auf. Die Begriffe sind dual zueinander.
Es sei
ein Prädikat und
eine Abbildung. EinAnamorphismus
ist dann so definiert:
![{\displaystyle {\begin{aligned}h:b&\mapsto Nil{\text{ falls }}p(b)=w\\b&\mapsto Cons(a,h(b')){\text{ mit }}[a,b']=g(b){\text{ falls }}p(b)=f\end{aligned}}}](/image.pl?url=https%3a%2f%2fwikimedia.org%2fapi%2frest_v1%2fmedia%2fmath%2frender%2fsvg%2f5e1e7a9918c6f0fc08606baa67510f37be83b4e2&f=jpg&w=240)
Der Anamorphismus mit Generator
und Prädikat
wird mit Linsenklammern notiert:
. Ein Beispiel für einen einfachen Anamorphismus ist die Funktion
. Sie errechnet aus einer natürlichen Zahl
die (umgedrehte) Liste der ersten
natürlichen Zahlen
.
Sei
ein Katamorphismus und
ein Anamorphismus. Dann ist die Abbildung
ein sogenannterHylomorphismus (altgriechischὕληhýlē, deutsch‚Holz, Material‘). Hylomorphismen sind in der Lage, einen ganzen Verarbeitungsprozess darzustellen, innerhalb dessen eine Struktur durch einen Anamorphismus entwickelt und durch einen Katamorphismus wieder eingedampft wird. Diese Darstellung ermöglicht es, die durch den Anamorphismus erzeugte Zwischenstruktur algebraisch zu entfernen.[1] Die daraus resultierende, eigenständige Darstellung des Hylomorphismus ohne Zwischenstruktur führt in der Praxis zu einer erheblichen Reduktion des benötigten Speicherplatzes.
Es ist problemlos möglich, einen komplexeren Algorithmus wie den Minimax-Algorithmus, der den besten Zug in einem Zweipersonenspiel wie Schach findet, als Hylomorphismus darzustellen.[1] Der Hylomorphismus
, der den Katamorphismus
zur Berechnung eines Zahlenprodukts mit dem oben genannten Anamorphismus
kombiniert, berechnet die Fakultät einer Zahl.
Im Gegensatz zuimperativen Programmen, die ausRechenanweisungen bestehen, sind funktionale Programme eine Menge von (Funktions)-Definitionen, die mathematisch einepartielle Abbildung von Eingabedaten auf Ausgabedaten sind und gleichzeitig selbst aus Funktionsaufrufen zusammengesetzt sind.
Ein typisches Beispiel ist die Berechnung derFakultät n! einer Zahl n (n! = 1 · 2 · 3 · … · n), hier eine imperative Lösung:
Eingabe: Zahl nAusgabe: Zahl b (= 1 · 2 · 3 · … · n)Algorithmus: b := 1 (Zuweisung)
solange n > 0fü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 Symbol:= imPseudocode repräsentiert). Zwar berechnet der Algorithmus die Fakultät der Zahl n, aber die Korrektheit dieses Rechenweges ist nicht offensichtlich.
Nun kann man die Fakultät jedoch auch mithilfe vonrekursiven Funktionen definieren, was zurfunktionalen Lösung führt.
Funktion f(n):wenn n > 0dann:Ausgabe: n · f(n - 1)sonst wenn n = 0dann:Ausgabe: 1
Die funktionale Programmierung kommt also ohne Schleifen und Zuweisungen aus, benötigt dafür aber Rekursion.
Die theoretische Grundlage der funktionalen Programmierung ist derλ-Kalkül (Lambda-Kalkül) vonAlonzo Church. Jeder Ausdruck und jeder Wert wird dabei als auswertbare Funktion betrachtet, so dass die ganze Programmierung auf der Übergabe von Funktionen als Parameter an Funktionen fußt.
Der Lambda-Kalkül erlaubt es, vollständige Teilausdrücke separat auszuwerten. Dies ist der wichtigste Vorteil gegenüber der imperativen Programmierung. Dieses Konzept vereinfacht dieProgrammverifikation undProgrammoptimierung, beispielsweise die Überführung der Programme in eineparallel auswertbare Form.
Historisch istLisp als die erste funktionale Programmiersprache aufzufassen. Sprachen der LISP-Familie (wie auchScheme) sinddynamisch typisiert. Seit der Entwicklung vonStandard-ML (SML) konzentriert sich die Forschung auf dem Gebiet der funktionalen Programmiersprachen auch aufstatisch typisierte Sprachen, insbesondere auf solche, die das Typsystem nachHindley und Milner verwenden. Der Vorteil dieses Typsystems ist die Verfügbarkeit vonparametrischem Polymorphismus zusammen mitTypinferenz: Programmierer müssen die Typen ihrer Funktionen und anderen Werte nicht angeben, sondern bekommen siegratis vom Übersetzer ausgerechnet, der zugleich noch während der Übersetzung Typfehler monieren kann. Dies wird allgemein als wesentlicher Vorteil gegenüber dynamisch typisierten Sprachen (Lisp,Python) aufgefasst, die zwar ebenfalls keine Typannotationen benötigen (im Gegensatz z. B. zuJava oderC), dafür aber Typfehler erst zur Laufzeit anmahnen können. Letzteres hat jedoch wiederum den Vorteil einer breiteren Anwendbarkeit bereits definierter Funktionen auf ggf. zum Entwicklungszeitpunkt noch nicht vorhergesehene neue Einsatzgebiete.
Das Hindley-Milner-System erlaubt allerdings nurPolymorphismus ersten Ranges; Erweiterungen für Polymorphismus zweiten und allgemein k-ten Ranges sind inzwischen in demHaskell-ÜbersetzerGHC als Erweiterungen verfügbar, bedingen jedoch wieder explizite Annotationen (Typinferenz ab Polymorphismus zweiten Ranges istunentscheidbar).
Rein funktionale Programmiersprachen fassen Programme als mathematische Funktion auf. Es gibtkeine Zustandsvariablen, die während einer Berechnung geändert werden. Um erwünschte Wirkungen (Benutzerinteraktion, Eingabe/Ausgabe-Operationen) beschreiben zu können, sind meist besondere Vorkehrungen notwendig. Die meisten funktionalen Programmiersprachen (Standard ML,Caml,Scheme und weitere) erlauben allerdings solche Wirkungen und sind daher keine reinen funktionalen Programmiersprachen.
Um Programmierung auch mit Wirkungen zu erlauben, ohne dabei zu einer sog. unreinen (englischimpure) Sprache zu werden, wird in der SpracheHaskell das aus derKategorientheorie stammende Konzept derMonaden verwendet (insbesondere vonEugenio Moggi undPhilip Wadler), welches Wirkbehaftung durchparametrische Typen ausdrückt und somit dasTypsystem dazu zwingt, zwischen Ausdrücken mit und Ausdrücken ohne Wirkungen zu unterscheiden. Auch inClean undMercury wird das Typsystem verwendet, um solche Wirkungen zu kennzeichnen. Dort benutzt man allerdings das Konzept der „Uniqueness“-Typen.
Man unterscheidet Funktionenerster Ordnung und Funktionenhöherer Ordnung. Bei Funktionen höherer Ordnung sind Funktionen selbst Werte. Dies erlaubt es insbesondere, Funktionen als Ergebnisse oder Argumente anderer Funktionen zu verwenden. Ein klassisches Beispiel ist derAbleitungsoperator, dessen Darstellbarkeit in Lisp obligatorisch für das Design dieser Sprache war: Eingabe ist eine differenzierbare Funktion, Ausgabe ist die Ableitung dieser Funktion.
Ein einfacheres Standardbeispiel für eine Funktion höherer Ordnung ist die Funktionmap, welche als Eingabe eine Funktionf und eine Listel erhält und die modifizierte Liste zurückgibt, die dadurch entsteht, dass die Funktionf auf jedes Element der Listel angewendet wird. Definition vonmap inHaskell:
mapf[]=[]mapf(x:xs)=fx:mapfxs
- In der ersten Zeile wird das Ergebnis für eine leere Liste [] zurückgegeben; die zweite Zeile wendet die Funktion
f auf das erste Listenelementx an und führt dann einenRekursionsschritt für die restliche Listexs durch.
Funktionale Sprachen kann man auch nach ihrerAuswertungsstrategie unterscheiden: Beistrikter Auswertung (englischeager bzw.strict evaluation) werden die Argumente von Funktionen zuerst ausgewertet. Dagegen werden bei dernicht-strikten Auswertung zunächst die Ausdrücke als Ganzes übergeben und dann ausgewertet.
Man kann zum Beispiel den Ausdruck
auf zwei Arten berechnen:
Im ersten Fall wird der Ausdruck strikt ausgewertet, 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 (nicht-strikte Auswertung).
Eine Variante der nicht-strikten Auswertung ist dieBedarfsauswertung (englischlazy evaluation), bei der Ausdrücke erst ausgewertet werden, wenn deren Wert in einer Berechnung benötigt wird. Dadurch lassen sich z. B. unendlich großeDatenstrukturen (die Liste aller natürlicher Zahlen, die Liste aller Primzahlen etc.) definieren und bestimmte Algorithmen vereinfachen sich.
Manche Berechnungen lassen sich mit strikter Auswertung, andere mit Bedarfsauswertung effizienter ausführen. Terminiert die strikte Auswertung eines Ausdruckes, so terminiert auch die nicht-strikte Auswertung. Hintergrund hiervon ist dieKonfluenz-Eigenschaft des jeder funktionalen Sprache zugrundeliegendenλ-Kalküls, die aussagt, dass das Ergebnis der Berechnung nicht von der Reihenfolge der Auswertung abhängt.
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 leistenDatenstrukturen für die Organisation von Daten. Sammlungen von guten Algorithmen und Datenstrukturen haben somit eine große praktische Bedeutung.
Der Verzicht auf 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 nach neuen Lösungen gesucht werden muss.
Chris Okasaki schreibt:
„Auch wenn die meisten dieser Bücher [über Datenstrukturen] behaupten, dass sie unabhängig von einer bestimmten Programmiersprache sind, so sind sie leider nur sprachunabhängig im SinneHenry 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 (ephemere Datenstrukturen), wohingegen funktionale Datenstrukturen mehrere Versionen verwalten (persistente Datenstrukturen).
Folgende Programme definieren eine Funktionringarea, die die Fläche berechnet, die zwischen den beiden konzentrischen Kreisen mit denRadienR undr bzw.r1 undr2 mit gemeinsamen Mittelpunkt liegt, entsprechend dem mathematischen Ausdruck:
. Dazu werden die Konstantepi und die Hilfsfunktionsq definiert. Diese werden vonringarea dann für die Berechnung benutzt. Anmerkung: Wir setzen voraus, dass
gilt.
(defunringarea(r1r2)(flet((sq(x)(*xx)))(*pi(-(sqr1)(sqr2)))))
letringarear1r2=letpi=3.14inletsqx=x*.xinpi*.(sqr1-.sqr2)
ringArea::(Floatinga)=>a->a->aringArear1r2=pi*(sqr1-sqr2)wheresqx=x*x
Der Typ der Funktionensq, ringArea ist polymorph und wird durch die Angabe der TypklasseFloating eingegrenzt. Die explizite Spezifikation des Typs ist optional und kann ebenso gut durch den Haskell-Compilerinferenziert werden. Pi ist in Haskell vordefiniert.
DEFINEpi == 3.14;sq == dup *;ringarea == sq swap sq swap - pi *.
Joy benutzt dieumgekehrte polnische Notation. Man beachte, dass hier alle Variablen Funktionen bezeichnen (auchpi ist eine Funktion).
pi=3.14;sq(x::Number)=x*x;ringarea(R::Number,r::Number)=pi*(sq(R)-sq(r));
frommathimportpisquared=lambdax:x**2ringarea=lambdar1,r2:pi*(squared(r1)-squared(r2))
squared=@(x)x.^2;ringarea=@(x,y)pi*(squared(x)-squared(y))
valsquared:Double=>Double=x=>x*xvalringArea:(Double,Double)=>Double=(outerRadius,innerRadius)=>math.Pi*(squared(outerRadius)-squared(innerRadius))
valsq:(Double)->Double={x->x*x}valringarea={R:Double,r:Double->PI*(sq(R)-sq(r))}Es gibt entweder die Möglichkeit, den Funktionstyp separat anzugeben (oben), oder die Parametertypen innerhalb des Lambdas zu definieren (unten).
UnaryOperator<Double>sq=d->d*d;BinaryOperator<Double>ringArea=(outerRadius,innerRadius)->Math.PI*(sq.apply(outerRadius)-sq.apply(innerRadius));
(define(ring-arear1r2)(definepi3.14)(define(sqx)(*xx))(*pi(-(sqr1)(sqr2))))
letvalpi=3.14valsq=fn(x:real)=>x*xinfunringarea(R,r)=pi*(sqR-sqr)end
Der Typ vonx muss hier explizit angegeben werden, da einSML97-Übersetzer sonst den Typint 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.
letpi=3.14letsquare={(x:Double)->Doubleinx*x}letringarea={(r1:Double,r2:Double)->Doubleinpi*(square(r1)-square(r2))}XSLT dient dem Transformieren von XML (insbesondere in XHTML) und hat zusammen mit XML stark an Bedeutung gewonnen. Sie ist funktional, wie Dimitre Novatchev gezeigt hat.[2] Die im folgenden Beispiel[3] definierte Funktion kehrt die Reihenfolge der Wörter einer Zeichenkette um. Typisch für funktionale Programmiersprachen ist der rekursive Aufruf. Der zweite Absatz zeigt, wie die Funktion verwendet wird.
<xsl:functionname="str:reverse"as="xs:string"><xsl:paramname="sentence"as="xs:string"/><xsl:choose><xsl:whentest="contains($sentence, ' ')"><xsl:sequenceselect="concat(str:reverse( substring-after($sentence, ' ')), ' ', substring-before($sentence, ' '))"/></xsl:when><xsl:otherwise><xsl:sequenceselect="$sentence"/></xsl:otherwise></xsl:choose></xsl:function><xsl:templatematch="/"><output><xsl:value-ofselect="str:reverse('DOG BITES MAN')"/></output></xsl:template>- Chris Okasaki:Purely Functional Data Structures. Cambridge University Press, 1999,ISBN 0-521-66350-4.
- Patrick M. KrusenottoFunktionale Programmierung und Metaprogrammierung – Interaktiv in Common Lisp. Springer, 2016,ISBN 978-3-658-13743-4.
- Lothar Piepmeyer:Grundkurs funktionale Programmierung mit Scala. Hanser, 2010,ISBN 978-3-446-42092-2, S. 297.
- Fethi Rabhi, Guy Lapalme:Algorithms – A Functional Programming Approach. Addison-Wesley, 1999,ISBN 0-201-59604-0.
- ↑abPatrick M. Krusenotto:Funktionale Programmierung und Metaprogrammierung. Springer, Wiesbaden 2016, S. 217ff, S. 244 ff.
- ↑The Functional Programming Language XSLT (Memento vom 5. Dezember 2008 imInternet Archive)
- ↑Aus derW3C Recommendation XSL Transformations (XSLT) Version 2.0