„Funktionale Programmierung“ – Versionsunterschied
[gesichtete Version] | [gesichtete Version] |
K Typo |
→Hylomorphismen: diakritische Zeichen Markierungen: Mobile Bearbeitung Mobile Web-Bearbeitung Erweiterte mobile Bearbeitung |
||
(109 dazwischenliegende Versionen von 64 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
'''Funktionale Programmierung''' ist ein [[Programmierparadigma]], |
'''Funktionale Programmierung''' ist ein [[Programmierparadigma]], in dem [[Funktion (Programmierung)|Funktionen]] 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. [[Programmiersprache]]n, die funktionale Programmierung ermöglichen, werden als funktionale Programmiersprachen bezeichnet. |
||
Die funktionale Programmierung entspringt der mathematischen Grundlagenforschung. In den 1930er Jahren entwickelte [[Alonzo Church]] den [[Lambda-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 funktionale Programmierung entspringt der mathematischen Grundlagenforschung. In den 1930er Jahren entwickelte [[Alonzo Church]] den [[Lambda-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 |
Die besonderen Eigenschaften der funktionalen Programmierung ermöglichen es, auf die, in der [[Imperative Programmierung|imperativen Programmierung]] benötigten, inneren Zustände eines Berechnungsprozesses ebenso zu verzichten, wie auf die zugehörigen Zustandsänderungen, die auch [[Wirkung (Informatik)|Seiteneffekte]] genannt werden. 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 |
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 von [[John McCarthy]] im Jahr 1958 erstellte, metazirkuläre [[ |
Die erste bedeutende, in funktionaler Programmierung erstellte Software ist der von [[John McCarthy]] im Jahr 1958 erstellte, metazirkuläre [[Lisp]]-Interpreter mit Namen [[eval]], 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. |
||
== Definition == |
== Definition == |
||
Zeile 14: | Zeile 14: | ||
# Computerprogramme werden als Funktionen verstanden, die für eine Eingabe eine Ausgabe liefern, die nur von dieser abhängig ist. |
# 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 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. |
# 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 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. |
# 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 ohne explizite Namensgebung literal in der Stellung |
# 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. |
||
== Funktionale Programmiersprachen == |
== Funktionale Programmiersprachen == |
||
Eine ''rein funktionale Programmiersprache'' wäre strikt [[Isomorphismus|isomorph]] zum [[Lambda-Kalkül]]. Das gilt mit minimalen Einschränkungen beispielsweise für die [[esoterische Programmiersprache]] ''unlambda''. |
|||
Eine ''funktionale Programmiersprache'' ist eine Programmiersprache, die die oben genannten Eigenschaften implementiert. Wichtige Vertreter funktionaler Programmiersprachen sind [[LISP]] und [[Haskell_(Programmiersprache)|Haskell]]. Fast alle in jüngster Zeit entstandenen Programmiersprachen gestatten funktionale Programmierung. |
|||
Wichtige Vertreter funktionaler Programmiersprachen sind die Lisp-Programmiersprachenfamilie und die rein funktionale Programmiersprache Haskell. |
|||
Zur funktionalen Programmierung gedachte Sprachen sind neben anderen LISP, ML, Haskell, OCaml, F#, Erlang, Clojure und Scala. 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. |
|||
Vornehmlich zur funktionalen Programmierung gedachte Sprachen sind: |
|||
* [[Clojure]] (2007) |
|||
* [[Elixir (Programmiersprache)|Elixir]] (2011) |
|||
* [[Erlang (Programmiersprache)|Erlang]] (1987) |
|||
* [[F-Sharp|F#]] (2002) |
|||
* [[Haskell (Programmiersprache)|Haskell]] (1990) |
|||
* [[LISP]] (1958) |
|||
* [[ML (Programmiersprache)|ML]] (1973) |
|||
* [[OCaml]] (1996) |
|||
* [[Scala (Programmiersprache)|Scala]] (2004). |
|||
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), [[Object Pascal|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 Sprachen, die keinerlei Möglichkeiten zur funktionalen Programmierung bieten, sind zum Beispiel C und Delphi. |
|||
Populäre Programmiersprachen, die keinerlei Möglichkeiten zur funktionalen Programmierung bieten, sind zum Beispiel C und VBA. |
|||
Unter einer ''rein funktionalen Programmiersprache'' wäre eine Programmiersprache zu verstehen, die isomorph zum Lambda-Kalkül ist. Das gilt mit minimalen Einschränkungen beispielsweise für die [[esoterische Programmiersprache]] ''unlambda''. |
|||
== Mathematische Konzepte == |
|||
Viele eigentlich objektorienterte Sprachen wurden in jüngsten Versionen aufgerüstet wie C++(ab Version 11) oder Java(ab Version 8). Allerdings leidet darunter die Syntax, sodass die Kompaktheit von LISP oder Haskell nicht erreicht wird. |
|||
Die allgemeingültigen Konzepte der funktionalen Programmierung entstammen der Mathematik der 1930er und 1940er Jahre. Von besonderer Bedeutung sind der [[Lambda-Kalkül]] und die [[Kategorientheorie]]. 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-Abblildung als neutrales Element definiert ist. Damit bilden die Funktionen eine „[[Gruppe (Mathematik)|Gruppe]] ohne das Gesetz vom inversen Element“. |
|||
=== Listen === |
|||
== Funktionen in Mathematik und Programmierung == |
|||
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 das [[Functional Programming System]] gibt, die explizit mit solchen Strukturen arbeiten. Listen und Bäume sind hingegen sehr gut mit der Funktionalen Programmierung verträglich. |
|||
Der Begriff der Funktion wird in der Programmierung und der [[Mathematik]] mit unterschiedlicher Bedeutung verwendet (vergleiche [[Funktion (Programmierung)]] und [[Funktion (Mathematik)]]). In der Mathematik wird eine Funktion als eine Abbildung der Funktionsargumente auf das jeweilige Funktionsergebnis betrachtet, so dass eine Funktion im mathematischen Sinne bei jeder Verwendung mit denselben Funktionsargumenten dasselbe Ergebnis liefert. Demgegenüber wird in der Informatik Funktion häufig als gleichbedeutend mit [[Unterprogramm]] verwendet. Anders als in der Mathematik brauchen in der Informatik Funktionen im allgemeinen Sprachgebrauch weder [[werttreu]] noch frei von einer [[Wirkung (Informatik)|Wirkung]] (auch als Nebenwirkung oder Nebeneffekt bezeichnet) zu sein: Erstens kann das Ergebnis eines Aufrufs neben den beim Aufruf übergebenen [[Funktionsparameter]]n auch von der Vorgeschichte der Funktionsaufrufe oder allgemeiner vom augenblicklichen Zustand des umgebenden [[Computerprogramm]]s abhängen, und zweitens kann der Funktionsaufruf selbst Auswirkungen auf das zukünftige Verhalten anderer Funktionen haben. |
|||
Sei <math>A</math> ein beliebiger Datentyp. Dann ist der Typ <math>A^*</math> der beliebig langen Listen mit mindestens 0 Elementen des Typs <math>A</math> gegeben durch die Rekursion <math>A^*=Nil|Cons(A,A^*)</math>. Dabei ist <math>Nil</math> die leere Liste und <math>Cons:A\times A^*\to A^*</math> eine zweistellige Operation, die aus einem Wert <math>a</math> und einer Liste <math>L</math> eine neue Liste <math>M</math> konstruiert, indem sie <math>a</math> vorne an <math>L</math> anhängt. |
|||
Die Funktionen funktionaler Programmiersprachen verhalten sich hingegen wie mathematische Funktionen. Dies wird unter anderem erreicht, indem eine Zuweisung der Form |
|||
:<code>x = x + 1</code> |
|||
ausgeschlossen wird. |
|||
Man kann diesen rekursiven Aufbau benutzen, um Funktionen <math>h \colon A^* \to B</math> zu schreiben, die eine Liste zerlegen und dabei einen Wert ermitteln. Eine solche Funktion <math>h</math> heißt Katamorphismus. |
|||
Mathematisch gelesen ergibt diese Zeile auch wenig Sinn, denn das wäre eine Gleichung und es käme |
|||
:<math>x = x + 1 \Leftrightarrow 0 = 1</math> |
|||
=== Katamorphismen === |
|||
heraus, was unwahr ist. Gemeint ist diese Zuweisung als |
|||
Sei <math>B</math> ein Datentyp, <math>b\in B</math> ein Wert und <math>\otimes \colon A\times B \to B</math> eine Abbildung. Dann ist durch |
|||
:<math>x := x + 1</math>, |
|||
:<math> |
|||
wobei das <math>x</math> auf der linken Seite ein anderes <math>x</math> 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 <math>x</math>, 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 ([[referenzielle Transparenz]]). |
|||
\begin{align} |
|||
h \colon A^*&\to B \\ |
|||
Nil&\mapsto b \\ |
|||
Cons(a,L)&\mapsto a\otimes h(L) |
|||
\end{align} |
|||
</math> |
|||
der Katamorphismus (zu griech. [[Liste griechischer Präfixe#K|κατά]] = entlang, herab) mit Initialwert <math>b</math> und Verknüpfung <math>\otimes</math> gegeben. In der üblichen Notation mit Bananenklammern wird er als <math>h=(|b,\otimes|)</math> geschrieben. Im Sinne der funktionalen Programmierung ist die Zirkumfix-Operation <math>(|\cdot,\cdot |)</math> eine Funktion höherer Ordnung. In funktionalen Programmiersprachen gibt es zur Berechnung von Katamorphismen die Funktion <code>reduce</code> oder <code>fold</code>. Ein Katamorphismus, der obiger Definition folgt, ist rechtshändig. Er entspricht der folgenden Programmschleife, die eine Liste von ihrem Ende her traversiert: |
|||
Ein Ansatz der funktionalen Programmierung ist, dass ihre Funktionen sich mehr wie mathematische Funktionen verhalten, damit die Rechen- und Beweismethoden der Mathematik besser auf Programme angewendet werden können (um vor allem ihre [[Korrektheit (Informatik)|Korrektheit]] zu beweisen). |
|||
:<math> |
|||
Sie vertritt weiter die Auffassung, dass 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]]). |
|||
\begin{align} |
|||
&x := b \\ |
|||
&For\;i := Length(L) \text{ Downto } 1 \text{ Do} \\ |
|||
&\;\;x := L_i \otimes x \\ |
|||
&End \\ |
|||
&Return(x) \\ |
|||
\end{align} |
|||
</math> |
|||
Ein linkshändiger Katamorphismus würde beim Index 1 beginnen. |
|||
Der Programmierer legt fest, wie die Funktionen zusammengesetzt sind, es gibt jedoch noch Freiheiten in der Auswertung, die von [[Compiler|Übersetzern]] zu Optimierungszwecken genutzt werden können. |
|||
Viele grundlegende Verfahren der Programmierung sind Katamorphismen. So berechnet <math>(|0,+|)</math> die Summe einer Zahlenliste, <math>(|\epsilon, \cdot |)</math> hängt Strings aneinander und <math>(|0,Inc|)</math> mit <math>Inc:(n,k)\mapsto k+1</math> berechnet die Länge einer Liste. |
|||
== Abgrenzung von imperativer Programmierung == |
|||
Eine Funktion, die eine Liste <math>l</math> nach Elementen filtert, die die Eigenschaft <math>p</math> erfüllen, kann mit der Funktion <math>filter</math> aus <math>p</math> errechnet werden, die wie folgt definiert ist: <math>filter:p\mapsto (|Nil,f|)</math> mit <math>f:Cons(a,L)\mapsto Cons(a,L)</math> ''falls'' <math>p(a)</math> ''und'' <math>f:Cons(a,L)\mapsto L</math> ''sonst''. |
|||
Ist die Operation <math>h</math> assoziativ mit dem neutralen Element <math>\emptyset</math>, dann ist der Katamorphismus <math>(|\emptyset,h|)</math> die eindeutige Erweiterung der Operation <math>h</math> 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 <math>f\circ g</math> bereits implementiert, so lässt sich die Komposition <math>\bigodot_{i=1}^n f_i</math> von <math>n</math> Funktionen als <math>(|id,\circ|)</math> darstellen (dabei ist <math>id</math> die identische Abbildung). |
|||
In der [[Imperative Programmierung|imperativen Programmierung]] sieht man ein Programm als Folge von Anweisungen (engl. ''statements''), die etwas tun, und dabei meistens Variablen verändern. In der funktionalen Programmierung besteht ein Programm aus Ausdrücken, die ausgewertet werden (''eval'' von Ausdrücken, ''apply'' von Funktionen). Beispiel: Eine imperative if-then-else-Konstruktion führt je nach Gültigkeit der Bedingung verschiedene Aktionen aus. Ein funktionaler if-then-else-Ausdruck ist eine Funktion, die je nach Gültigkeit der Bedingung einen if- oder einen else-Wert zurückgibt. |
|||
=== Anamorphismen === |
|||
Im Gegensatz zu [[Imperative Programmierung|imperativen Programmen]], die aus ''Rechenanweisungen'' bestehen, sind funktionale Programme eine Menge von (Funktions)-''Definitionen'', die man mathematisch als [[partielle Abbildung]]en von Eingabedaten auf Ausgabedaten auffassen kann. |
|||
Während Katamorphismen Listen zu Einzelwerten reduzieren, bauen Anamorphismen Listen aus Einzelwerten auf. Die Begriffe sind dual zueinander. |
|||
Es sei <math>p \colon B\to \{w,f\}</math> ein Prädikat und <math>g \colon B\to A\times B</math> eine Abbildung. Ein ''Anamorphismus'' <math>h \colon B\to A^*</math> ist dann so definiert: |
|||
Ein typisches Beispiel ist die Berechnung der [[Fakultät (Mathematik)|Fakultät]] n! einer Zahl n (n! = 1 · 2 · 3 · ... · n), hier eine imperative Lösung: |
|||
< |
:<math> |
||
\begin{align} |
|||
;Eingabe: Zahl n |
|||
h:b&\mapsto Nil \text{ falls } p(b)=w \\ |
|||
;Ausgabe: Zahl b (=1 · 2 · 3 · ... · n) |
|||
b&\mapsto Cons(a,h(b'))\text{ mit } [a,b'] = g(b) \text{ falls } p(b)=f |
|||
;Algorithmus: |
|||
\end{align} |
|||
:b := 1 (Zuweisung) |
|||
</math> |
|||
:'''solange''' n > 0 '''führe aus''': |
|||
:::b := n · b |
|||
Der Anamorphismus mit Generator <math>g</math> und Prädikat <math>p</math> wird mit Linsenklammern notiert: <math> h = [(p,g)]</math>. Ein Beispiel für einen einfachen Anamorphismus ist die Funktion <math>\iota=[(i\mapsto (i<1),i \mapsto [i,i-1] )]</math>. Sie errechnet aus einer natürlichen Zahl <math>n</math> die (umgedrehte) Liste der ersten <math>n</math> natürlichen Zahlen <math>\iota(n)=[n,n-1,n-2,..,1]</math>. |
|||
:::n := n − 1 |
|||
:''Ausgabe:'' b |
|||
=== Hylomorphismen === |
|||
</code> |
|||
Sei <math>(|z,f|)</math> ein Katamorphismus und <math>[(p,g)]</math> ein Anamorphismus. Dann ist die Abbildung <math>(|z,f|)\circ[(p,g)]</math> ein sogenannter ''Hylomorphismus'' ({{grcS|ὕλη|hýlē|de=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.<ref name="Krusenotto">Patrick M. Krusenotto: ''Funktionale Programmierung und Metaprogrammierung.'' Springer, Wiesbaden 2016, S. 217ff, S. 244 ff.</ref> 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.<ref name="Krusenotto" /> Der Hylomorphismus <math>!=(|1,\times|)\circ([(i\mapsto (i<1),i \mapsto [i,i-1])]</math>, der den Katamorphismus <math>(|1,\times|)</math> zur Berechnung eines Zahlenprodukts mit dem oben genannten Anamorphismus <math>\iota</math> kombiniert, berechnet die Fakultät einer Zahl. |
|||
== Abgrenzung von imperativer Programmierung == |
|||
Im Gegensatz zu [[Imperative Programmierung|imperativen Programmen]], die aus ''Rechenanweisungen'' bestehen, sind funktionale Programme eine Menge von (Funktions)-''Definitionen'', die mathematisch eine [[partielle Abbildung]] von Eingabedaten auf Ausgabedaten sind und gleichzeitig selbst aus Funktionsaufrufen zusammengesetzt sind. |
|||
Ein typisches Beispiel ist die Berechnung der [[Fakultät (Mathematik)|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 Symbol <code>:=</code> im [[Pseudocode]] repräsentiert). Zwar berechnet der Algorithmus die Fakultät der Zahl n, aber die Korrektheit dieses Rechenweges ist nicht offensichtlich. |
Charakteristisch für die imperative Programmierung sind hier die Zuweisungen (Änderung von Werten, durch das Symbol <code>:=</code> im [[Pseudocode]] repräsentiert). Zwar berechnet der Algorithmus die Fakultät der Zahl n, aber die Korrektheit dieses Rechenweges ist nicht offensichtlich. |
||
Zeile 74: | Zeile 118: | ||
<math>n! = f(n) = \begin{cases} n \cdot f(n-1) &\mathrm{f\ddot ur}\ \ n > 0\\ 1 &\mathrm{f\ddot ur}\ \ n = 0\end{cases}</math> |
<math>n! = f(n) = \begin{cases} n \cdot f(n-1) &\mathrm{f\ddot ur}\ \ n > 0\\ 1 &\mathrm{f\ddot ur}\ \ n = 0\end{cases}</math> |
||
'''Funktion f(n):''' |
|||
<code> |
|||
'''wenn''' n > 0 '''dann''': |
|||
;Funktion f(n): |
|||
''Ausgabe'': n · f(n - 1) |
|||
:'''wenn''' n > 0 '''dann''': |
|||
'''sonst wenn''' n = 0 '''dann''': |
|||
::''Ausgabe'': n·f(n-1) |
|||
''Ausgabe'': 1 |
|||
::''Ausgabe'': 1 |
|||
</code> |
|||
Die funktionale Programmierung kommt also ohne Schleifen und Zuweisungen aus, benötigt dafür aber Rekursion. |
Die funktionale Programmierung kommt also ohne Schleifen und Zuweisungen aus, benötigt dafür aber Rekursion. |
||
=== Mächtigkeit funktionaler Sprachen === |
|||
Aus dem anderen Programmierparadigma ergibt sich im Vergleich zu den verbreiteteren imperativen Sprachen eine andere Herangehensweise bei der Konzeption von Programmen. Die wesentlichen Unterschiede manifestieren sich im Software-Engineering und in der Performance. Im Allgemeinen ergeben sich Vorteile bei der Konzeption und Wartbarkeit von Programmen, während sich die hohe Abstraktion von der maschinellen Implementierung in performance-kritischen Anwendungen als Nachteil erweist. |
|||
== Lambda-Kalkül == |
== Lambda-Kalkül == |
||
Die theoretische Grundlage der funktionalen Programmierung ist der [[Lambda-Kalkül|λ-Kalkül]] (Lambda-Kalkül) von [[Alonzo 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 die [[Verifikation#Informatik|Programmverifikation]] und [[Programmoptimierung]], beispielsweise die Überführung der Programme in eine [[Parallele Programmierung|parallel auswertbare Form]]. |
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 die [[Verifikation#Informatik|Programmverifikation]] und [[Programmoptimierung]], beispielsweise die Überführung der Programme in eine [[Parallele Programmierung|parallel auswertbare Form]]. |
||
== Typsystem == |
== Typsystem == |
||
Historisch ist [[Lisp]] als die erste funktionale Programmiersprache aufzufassen |
Historisch ist [[Lisp]] als die erste funktionale Programmiersprache aufzufassen. Sprachen der LISP-Familie (wie auch [[Scheme]]) sind [[Dynamische Typisierung|dynamisch typisiert]]. Seit der Entwicklung von [[Standard ML|Standard-ML (SML)]] konzentriert sich die Forschung auf dem Gebiet der funktionalen Programmiersprachen auch auf [[Statische Typisierung|statisch typisierte]] Sprachen, insbesondere auf solche, die das Typsystem nach [[Hindley-Milner|Hindley und Milner]] verwenden. Der Vorteil dieses Typsystems ist die Verfügbarkeit von [[Polymorphie (Programmierung)|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 typisierten Sprachen (Lisp, [[Python (Programmiersprache)|Python]]) aufgefasst, die zwar ebenfalls keine Typannotationen benötigen (im Gegensatz z. B. zu [[Java (Programmiersprache)|Java]] oder [[C (Programmiersprache)|C]]), 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 nur [[Polymorphismus ersten Ranges]]; Erweiterungen für Polymorphismus zweiten und allgemein k-ten Ranges sind inzwischen in dem [[Haskell (Programmiersprache)|Haskell]]-Übersetzer ''GHC'' als Erweiterungen verfügbar, bedingen jedoch wieder explizite Annotationen (Typinferenz ab Polymorphismus zweiten Ranges ist [[Entscheidbarkeit|unentscheidbar]]). |
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 (Programmiersprache)|Haskell]]-Übersetzer ''GHC'' als Erweiterungen verfügbar, bedingen jedoch wieder explizite Annotationen (Typinferenz ab Polymorphismus zweiten Ranges ist [[Entscheidbarkeit|unentscheidbar]]). |
||
== Rein funktionale Programmiersprachen == |
== Rein funktionale Programmiersprachen == |
||
Rein |
Rein funktionale Programmiersprachen fassen Programme als mathematische Funktion auf. Es gibt ''keine 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 ( |
Um Programmierung auch mit Wirkungen zu erlauben, ohne dabei zu einer sog. unreinen (englisch ''impure'') Sprache zu werden, wird in der Sprache [[Haskell (Programmiersprache)|Haskell]] das aus der [[Kategorientheorie]] stammende Konzept der [[Monade (Informatik)|Monaden]] verwendet (insbesondere von [[Eugenio Moggi]] und [[Philip Wadler]]), welches Wirkbehaftung durch [[Parametrischer Typ|parametrische Typen]] ausdrückt und somit das [[Typsystem]] dazu zwingt, zwischen Ausdrücken mit und Ausdrücken ohne Wirkungen zu unterscheiden. Auch in [[Clean (Programmiersprache)|Clean]] und [[Mercury (Programmiersprache)|Mercury]] wird das Typsystem verwendet, um solche Wirkungen zu kennzeichnen. Dort benutzt man allerdings das Konzept der „Uniqueness“-Typen. |
||
== Funktionen höherer Ordnung == |
== 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 Argumente anderer Funktionen zu verwenden. Ein |
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 Argumente anderer Funktionen zu verwenden. Ein klassisches Beispiel ist der [[Differentialrechnung#Ableitungsregeln|Ableitungsoperator]], 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 Funktion <code>map</code>, welche als Eingabe eine Funktion <code>f</code> und eine Liste <code>l</code> erhält und die modifizierte Liste zurückgibt, die dadurch entsteht, dass die Funktion <code>f</code> auf jedes Element der Liste <code>l</code> angewendet wird. Definition von <code>map</code> in [[Haskell (Programmiersprache)|Haskell]]: |
|||
<syntaxhighlight lang="haskell"> |
<syntaxhighlight lang="haskell"> |
||
Zeile 113: | Zeile 153: | ||
: In der ersten Zeile wird das Ergebnis für eine leere Liste [] zurückgegeben; die zweite Zeile wendet die Funktion <code>f</code> auf das erste Listenelement ''x'' an und führt dann einen [[Rekursion]]sschritt für die restliche Liste ''xs'' durch. |
: In der ersten Zeile wird das Ergebnis für eine leere Liste [] zurückgegeben; die zweite Zeile wendet die Funktion <code>f</code> auf das erste Listenelement ''x'' an und führt dann einen [[Rekursion]]sschritt für die restliche Liste ''xs'' durch. |
||
== Bedarfsauswertung und strikte Auswertung == |
== {{Anker|Bedarfsauswertung}} Bedarfsauswertung und strikte Auswertung == |
||
Funktionale Sprachen kann man auch nach ihrer [[Auswertung (Informatik)|Auswertungsstrategie]] unterscheiden: Bei ''strikter Auswertung'' ( |
Funktionale Sprachen kann man auch nach ihrer [[Auswertung (Informatik)|Auswertungsstrategie]] unterscheiden: Bei ''strikter Auswertung'' (englisch ''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 kann zum Beispiel den Ausdruck <math>(3+5)^2</math> auf zwei Arten berechnen: |
Man kann zum Beispiel den Ausdruck <math>(3+5)^2</math> auf zwei Arten berechnen: |
||
Zeile 123: | Zeile 163: | ||
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). |
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 die ''[[Bedarfsauswertung]]'' ( |
Eine Variante der nicht-strikten Auswertung ist die ''[[Bedarfsauswertung]]'' (englisch ''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 [[Datenstruktur]]en (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 die [[Konfluenz (Informatik)|Konfluenz]]-Eigenschaft des jeder funktionalen Sprache zugrundeliegenden [[Lambda-Kalkül|λ-Kalküls]], die aussagt, dass das Ergebnis der Berechnung nicht von der Reihenfolge der Auswertung abhängt. |
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 die [[Konfluenz (Informatik)|Konfluenz]]-Eigenschaft des jeder funktionalen Sprache zugrundeliegenden [[Lambda-Kalkül|λ-Kalküls]], die aussagt, dass das Ergebnis der Berechnung nicht von der Reihenfolge der Auswertung abhängt. |
||
Zeile 134: | Zeile 174: | ||
[[Chris Okasaki]] schreibt: |
[[Chris Okasaki]] schreibt: |
||
{{Zitat |
|||
{{Zitat|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 Sinne [[Henry Ford II|Henry Ford]]s: Programmierer können jede Programmiersprache benutzen, solange sie imperativ ist.}} |
|||
|Text=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 Sinne [[Henry Ford II|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 (ephemere Datenstrukturen), wohingegen funktionale Datenstrukturen mehrere Versionen verwalten (persistente Datenstrukturen). |
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). |
||
== Beispiele == |
== Beispiele == |
||
Folgende Programme definieren eine Funktion <code>ringarea</code>, die die Fläche berechnet, die zwischen den beiden konzentrischen Kreisen mit den [[Radius|Radien]] <code>R</code> und <code>r</code> bzw. <code>r1</code> und <code>r2</code> mit gemeinsamen Mittelpunkt liegt, entsprechend dem mathematischen Ausdruck: <math>A = \pi \cdot (r_1^2-r_2^2)</math>. Dazu werden die Konstante <code>pi</code> und die Hilfsfunktion <code>sq</code> definiert. Diese werden von <code>ringarea</code> dann für die Berechnung benutzt. Anmerkung: Wir setzen voraus, dass <math>r_1\geq r_2</math> gilt. |
|||
Folgende Programme definieren eine Funktion <code>ringarea</code>, die die Fläche berechnet, die zwischen den beiden konzentrischen Kreisen mit den [[Radius|Radien]] <code>R</code> und <code>r</code> bzw. <code>r1</code> und <code>r2</code> mit gemeinsamen Mittelpunkt liegt. Dazu werden die Konstante <code>pi</code> und die Hilfsfunktion <code>sq</code> definiert. Diese werden von <code>ringarea</code> dann für die Berechnung benutzt. |
|||
=== [[Common Lisp]] === |
=== [[Common Lisp]] === |
||
<syntaxhighlight lang="common-lisp"> |
<syntaxhighlight lang="common-lisp"> |
||
(defun ringarea (r1 r2) |
(defun ringarea (r1 r2) |
||
(flet ((sq (x) |
(flet ((sq (x) |
||
(* x x))) |
|||
(* pi |
(* pi (- (sq r1) |
||
(sq r2))))) |
|||
(sq r2))))) |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
=== [[F-Sharp|F#]] und [[OCaml]] === |
=== [[F-Sharp|F#]] und [[OCaml]] === |
||
<syntaxhighlight lang="fsharp"> |
<syntaxhighlight lang="fsharp"> |
||
let ringarea (r1, r2) = |
|||
let pi = 3.14 in |
let pi = 3.14 in |
||
let sq x = x*x in |
let sq x = x*x in |
||
pi*(sq r1 - sq r2) |
pi * (sq r1 - sq r2) |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
=== [[Haskell (Programmiersprache)|Haskell]] === |
=== [[Haskell (Programmiersprache)|Haskell]] === |
||
<syntaxhighlight lang=" |
<syntaxhighlight lang="haskell"> |
||
ringArea :: (Floating a) => a -> a -> a |
|||
ringArea r1 r2 = pi * (sq r1 - sq r2) where |
|||
sq x = x^2 |
|||
sq x = x * x |
|||
ringArea :: (Floating a) => a -> a -> a -- optionale explizite Typangabe |
|||
ringArea r1 r2 = pi*(sq r1- sq r2) |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
Der Typ der Funktionen <code>sq, ringArea</code> ist polymorph und wird durch die Angabe der Typklasse <code>Floating</code> eingegrenzt. Die explizite Spezifikation des Typs ist optional und kann ebenso gut durch den Haskell-Compiler [[Typableitung|inferenziert]] werden. Pi ist in Haskell vordefiniert. |
Der Typ der Funktionen <code>sq, ringArea</code> ist polymorph und wird durch die Angabe der Typklasse <code>Floating</code> eingegrenzt. Die explizite Spezifikation des Typs ist optional und kann ebenso gut durch den Haskell-Compiler [[Typableitung|inferenziert]] werden. Pi ist in Haskell vordefiniert. |
||
=== [[Joy (Programmiersprache)|Joy]] === |
|||
Hier eine komplexere Implementation, ausschließlich mit Hilfe von Funktionen höherer Ordnung: |
|||
:; <code>DEFINE</code> |
|||
:: <code>pi == 3.14;</code> |
|||
:: <code>sq == dup *;</code> |
|||
:: <code>ringarea == sq swap sq swap - pi *.</code> |
|||
<syntaxhighlight lang="Haskell"> |
|||
ringArea' :: (Floating a) => a -> a -> a -- optionale explizite Typangabe |
|||
ringArea' r1 r2 = foldr (-) 0 $ map ((*pi) . (^2)) [r1, r2] |
|||
</syntaxhighlight> |
|||
=== [[Joy (Programmiersprache)|Joy]] === |
|||
<code> |
|||
:;DEFINE |
|||
::pi == 3.14; |
|||
::sq == dup *; |
|||
::ringarea == sq swap sq swap - pi *. |
|||
</code> |
|||
[[Joy (Programmiersprache)|Joy]] benutzt die [[umgekehrte polnische Notation]]. Man beachte, dass hier alle Variablen Funktionen bezeichnen (auch ''pi'' ist eine Funktion). |
[[Joy (Programmiersprache)|Joy]] benutzt die [[umgekehrte polnische Notation]]. Man beachte, dass hier alle Variablen Funktionen bezeichnen (auch ''pi'' ist eine Funktion). |
||
=== [[Julia (Programmiersprache)|Julia]] === |
=== [[Julia (Programmiersprache)|Julia]] === |
||
<syntaxhighlight lang=" |
<syntaxhighlight lang="julia"> |
||
pi=3.14; |
pi = 3.14; |
||
sq(x::Number) = x * x; |
sq(x::Number) = x * x; |
||
ringarea(R::Number, r::Number) = pi*(sq(R) - sq(r)); |
ringarea(R::Number, r::Number) = pi * (sq(R) - sq(r)); |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
=== [[Python (Programmiersprache)|Python]] === |
|||
Implementierung mit höheren Funktionen: |
|||
<syntaxhighlight lang="python"> |
|||
from math import pi |
|||
<syntaxhighlight lang="Julia"> |
|||
squared = lambda x: x ** 2 |
|||
ringarea(R1::Number, r::Number) = pi*((R^2) - (r^2)); |
|||
ringarea = lambda r1, r2: pi * (squared(r1) - squared(r2)) |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
=== [[MATLAB|Matlab]] === |
|||
Oder als anonyme Funktion mit den beiden Werten R = 4 und r = 3: |
|||
<syntaxhighlight lang="matlab"> |
|||
squared = @(x) x.^2; |
|||
<syntaxhighlight lang="Julia"> |
|||
( |
ringarea = @(x,y) pi * (squared(x) - squared(y)) |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
=== Makefile === |
|||
Jedes [[Make#Beispiel_f.C3.BCr_ein_Makefile|Makefile]] stellt ein funktionales Programm dar. |
|||
=== [[Scala (Programmiersprache)|Scala]] === |
=== [[Scala (Programmiersprache)|Scala]] === |
||
<syntaxhighlight lang="scala"> |
<syntaxhighlight lang="scala"> |
||
val squared: Double => Double = x => x * x |
|||
val ringArea: (Double, Double) => Double = |
|||
(outerRadius, innerRadius) => |
|||
math.Pi * (squared(outerRadius) - squared(innerRadius)) |
|||
</syntaxhighlight> |
|||
=== [[Kotlin (Programmiersprache)|Kotlin]] === |
|||
def ringarea(R: Double, r: Double) = pi * (sq(R) - sq(r)) |
|||
<syntaxhighlight lang="kotlin"> |
|||
val sq: (Double) -> Double = { x -> x * x } |
|||
val ringarea = { R: Double, r: Double -> PI * (sq(R) - sq(r)) } |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
Es gibt entweder die Möglichkeit, den Funktionstyp separat anzugeben (oben), oder die Parametertypen innerhalb des Lambdas zu definieren (unten). |
|||
=== [[ |
=== [[Java (Programmiersprache)|Java]] === |
||
<syntaxhighlight lang=" |
<syntaxhighlight lang="java"> |
||
UnaryOperator<Double> sq = d -> d * d; |
|||
(define (ringarea r1 r2) |
|||
BinaryOperator<Double> ringArea = (outerRadius, innerRadius) -> Math.PI * (sq.apply(outerRadius) - sq.apply(innerRadius)); |
|||
(define pi 3.14) |
|||
(define (sq x) (* x x)) |
|||
(* pi (- (sq r1) (sq r2))) |
|||
) |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
Alternativ mit Funktionen höherer Ordnung: |
|||
=== [[Scheme]] === |
|||
<syntaxhighlight lang="scheme"> |
|||
<syntaxhighlight lang="scheme" line="1"> |
|||
(define (ringarea r1 r2) |
|||
(define (ring-area r1 r2) |
|||
(* 3.14 (foldr - 0 (map (lambda (x) (* x x)) (list r1 r2))))) |
|||
(define pi 3.14) |
|||
(define (sq x) |
|||
(* x x)) |
|||
(* pi (- (sq r1) |
|||
(sq r2)))) |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
=== [[Standard ML|SML]] === |
=== [[Standard ML|SML]] === |
||
<syntaxhighlight lang="sml"> |
<syntaxhighlight lang="sml"> |
||
let |
|||
local |
|||
val pi = 3.14 |
val pi = 3.14 |
||
val sq = fn (x:real) => x*x |
val sq = fn (x: real) => x * x |
||
in |
in |
||
fun ringarea(R, r) = pi*(sq R - sq r) |
fun ringarea(R, r) = pi * (sq R - sq r) |
||
end |
end |
||
</syntaxhighlight> |
</syntaxhighlight> |
||
Der Typ von <code>x</code> muss hier explizit angegeben werden, da ein [[SML97]]-Übersetzer sonst den Typ ''int'' inferieren würde. Das zugrundeliegende Problem ist das der [[Überladen#Operatorüberladung|Überladung]] von Operatoren; dieses Problem wurde erst mit Einführung von Typklassen gelöst, allerdings in Haskell und verwandten Sprachen. |
Der Typ von <code>x</code> muss hier explizit angegeben werden, da ein [[SML97]]-Übersetzer sonst den Typ ''int'' inferieren würde. Das zugrundeliegende Problem ist das der [[Überladen#Operatorüberladung|Überladung]] von Operatoren; dieses Problem wurde erst mit Einführung von Typklassen gelöst, allerdings in Haskell und verwandten Sprachen. |
||
=== [[Swift (Programmiersprache)|Swift]] === |
|||
<syntaxhighlight lang="fsharp"> |
|||
let pi = 3.14 |
|||
let square = { (x: Double) -> Double in x * x } |
|||
let ringarea = { (r1: Double, r2: Double) -> Double in |
|||
pi * (square(r1) - square(r2)) |
|||
} |
|||
</syntaxhighlight> |
|||
=== XSLT === |
=== XSLT === |
||
[[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.<ref>{{Webarchiv |
[[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.<ref>{{Webarchiv|url=http://www.topxml.com/xsl/articles/fp | wayback=20081205113752 | text=The Functional Programming Language XSLT}}</ref> Die im folgenden Beispiel<ref>Aus der [https://www.w3.org/TR/2007/REC-xslt20-20070123/#stylesheet-functions W3C Recommendation XSL Transformations (XSLT) Version 2.0]</ref> 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. |
||
<syntaxhighlight lang="xml"> |
<syntaxhighlight lang="xml"> |
||
<xsl:function name="str:reverse" as="xs:string"> |
<xsl:function name="str:reverse" as="xs:string"> |
||
Zeile 266: | Zeile 312: | ||
</xsl:template> |
</xsl:template> |
||
</syntaxhighlight> |
|||
=== [[Swift_(Programmiersprache)|Swift]] === |
|||
<syntaxhighlight lang="fsharp"> |
|||
let pi = 3.14 |
|||
let square = {(x: Double) -> Double in x * x } |
|||
let ringarea = {(r1: Double, r2: Double) -> Double in |
|||
pi * (square(r1) - square(r2)) |
|||
} |
|||
</syntaxhighlight> |
</syntaxhighlight> |
||
Zeile 283: | Zeile 319: | ||
== Literatur == |
== Literatur == |
||
* Chris Okasaki: ''Purely Functional Data Structures.'' Cambridge University Press 1999, ISBN 0-521-66350-4 |
* Chris Okasaki: ''Purely Functional Data Structures.'' Cambridge University Press, 1999, ISBN 0-521-66350-4. |
||
* Patrick M. Krusenotto ''Funktionale Programmierung und Metaprogrammierung – Interaktiv in Common Lisp'' Springer 2016, ISBN 978-3-658-13743-4 |
* Patrick M. Krusenotto ''Funktionale 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 |
* Fethi Rabhi, Guy Lapalme: ''Algorithms – A Functional Programming Approach.'' Addison-Wesley, 1999, ISBN 0-201-59604-0. |
||
== Weblinks == |
== Weblinks == |
||
* [https://www.infosun.fim.uni-passau.de/cl/lehre/funcprog05/wasistfp.html ''Was ist funktionale Programmierung?''], Fakultät für Informatik und Mathematik der [[Universität Passau]] |
|||
* [http://www.stanford.edu/class/cs242/readings/backus.pdf ''Can Programming Be Liberated from the von Neumann Style? A Functional Style and Its Algebra of Programs''] − Vorlesung von [[John W. Backus]] anlässlich der Verleihung des [[Turing-Preis]]es (1977; PDF; 2,87 MB) |
|||
* Amr Sabry: [ |
* Amr Sabry: [https://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.27.7800 ''What is a Purely Functional Language?''] In: ''J. Functional Programming.'' 8(1), Januar 1998, S. 1–22. |
||
* Carsten König'': [https://www.informatik-aktuell.de/entwicklung/programmiersprachen/was-ist-funktionale-programmierung.html Was ist funktionale Programmierung?]'', [[Informatik Aktuell (Magazin)]] |
|||
* [http://www.linux-magazin.de/Online-Artikel/Funktionale-Programmierung-1-Grundzuege ''Funktionale Programmierung (1): Raus aus der akademischen Nische ''] Artikel von Rainer Grimm bei Linux-Magazin Online |
|||
* Andre Schütz: [https://www.informatik-aktuell.de/entwicklung/programmiersprachen/funktionale-programmierung-mit-scala-herangehensweisen-und-konzepte.html ''Funktionale Programmierung mit Scala: Einführung''], [[Informatik Aktuell (Magazin)]] |
|||
== |
== Einzelnachweise == |
||
<references /> |
<references /> |
||
[[Kategorie:Programmiersprache als Thema]] |
|||
[[Kategorie:Programmierparadigma]] |
[[Kategorie:Programmierparadigma]] |
Aktuelle Version vom 6. März 2025, 22:59 Uhr
Funktionale Programmierung ist ein Programmierparadigma, in dem Funktionen 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 entwickelte Alonzo Church den Lambda-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 der imperativen Programmierung benötigten, inneren Zustände eines Berechnungsprozesses ebenso zu verzichten, wie auf die zugehörigen Zustandsänderungen, die auch Seiteneffekte genannt werden. 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 von John McCarthy im Jahr 1958 erstellte, metazirkuläre Lisp-Interpreter mit Namen eval, 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.
Definition
[Bearbeiten | Quelltext bearbeiten]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.
Funktionale Programmiersprachen
[Bearbeiten | Quelltext bearbeiten]Eine rein funktionale Programmiersprache wäre strikt isomorph zum Lambda-Kalkül. Das gilt mit minimalen Einschränkungen beispielsweise für die esoterische Programmiersprache unlambda.
Wichtige Vertreter funktionaler Programmiersprachen sind die Lisp-Programmiersprachenfamilie und die rein funktionale Programmiersprache Haskell. Vornehmlich zur funktionalen Programmierung gedachte Sprachen sind:
- Clojure (2007)
- Elixir (2011)
- Erlang (1987)
- F# (2002)
- Haskell (1990)
- LISP (1958)
- ML (1973)
- OCaml (1996)
- Scala (2004).
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.
Mathematische Konzepte
[Bearbeiten | Quelltext bearbeiten]Die allgemeingültigen Konzepte der funktionalen Programmierung entstammen der Mathematik der 1930er und 1940er Jahre. Von besonderer Bedeutung sind der Lambda-Kalkül und die Kategorientheorie. 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-Abblildung als neutrales Element definiert ist. Damit bilden die Funktionen eine „Gruppe ohne das Gesetz vom inversen Element“.
Listen
[Bearbeiten | Quelltext bearbeiten]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 das Functional 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.
Katamorphismen
[Bearbeiten | Quelltext bearbeiten]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 Funktion reduce
oder fold
. 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).
Anamorphismen
[Bearbeiten | Quelltext bearbeiten]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. Ein Anamorphismus ist dann so definiert:
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 .
Hylomorphismen
[Bearbeiten | Quelltext bearbeiten]Sei ein Katamorphismus und ein Anamorphismus. Dann ist die Abbildung ein sogenannter Hylomorphismus (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.
Abgrenzung von imperativer Programmierung
[Bearbeiten | Quelltext bearbeiten]Im Gegensatz zu imperativen Programmen, die aus Rechenanweisungen bestehen, sind funktionale Programme eine Menge von (Funktions)-Definitionen, die mathematisch eine partielle Abbildung von Eingabedaten auf Ausgabedaten sind und gleichzeitig selbst aus Funktionsaufrufen zusammengesetzt sind.
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 Symbol :=
im Pseudocode 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 von rekursiven Funktionen definieren, was zur funktionalen Lösung führt.
Funktion f(n): wenn n > 0 dann: Ausgabe: n · f(n - 1) sonst wenn n = 0 dann: Ausgabe: 1
Die funktionale Programmierung kommt also ohne Schleifen und Zuweisungen aus, benötigt dafür aber Rekursion.
Lambda-Kalkül
[Bearbeiten | Quelltext bearbeiten]Die theoretische Grundlage der funktionalen Programmierung ist der λ-Kalkül (Lambda-Kalkül) von Alonzo 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 die Programmverifikation und Programmoptimierung, beispielsweise die Überführung der Programme in eine parallel auswertbare Form.
Typsystem
[Bearbeiten | Quelltext bearbeiten]Historisch ist Lisp als die erste funktionale Programmiersprache aufzufassen. Sprachen der LISP-Familie (wie auch Scheme) sind dynamisch typisiert. Seit der Entwicklung von Standard-ML (SML) konzentriert sich die Forschung auf dem Gebiet der funktionalen Programmiersprachen auch auf statisch typisierte 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 typisierten 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. 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 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
[Bearbeiten | Quelltext bearbeiten]Rein funktionale Programmiersprachen fassen Programme als mathematische Funktion auf. Es gibt keine 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 (englisch impure) Sprache zu werden, wird in der Sprache Haskell das aus der Kategorientheorie stammende Konzept der Monaden verwendet (insbesondere von Eugenio Moggi und Philip Wadler), welches Wirkbehaftung durch parametrische Typen ausdrückt und somit das Typsystem dazu zwingt, zwischen Ausdrücken mit und Ausdrücken ohne Wirkungen zu unterscheiden. Auch in Clean und Mercury wird das Typsystem verwendet, um solche Wirkungen zu kennzeichnen. Dort benutzt man allerdings das Konzept der „Uniqueness“-Typen.
Funktionen höherer Ordnung
[Bearbeiten | Quelltext bearbeiten]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 Argumente anderer Funktionen zu verwenden. Ein klassisches Beispiel ist der Ableitungsoperator, 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 Funktion map
, welche als Eingabe eine Funktion f
und eine Liste l
erhält und die modifizierte Liste zurückgibt, die dadurch entsteht, dass die Funktion f
auf jedes Element der Liste l
angewendet wird. Definition von map
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 strikte Auswertung
[Bearbeiten | Quelltext bearbeiten]Funktionale Sprachen kann man auch nach ihrer Auswertungsstrategie unterscheiden: Bei strikter Auswertung (englisch 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 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 die Bedarfsauswertung (englisch 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 strikter Auswertung, andere mit Bedarfsauswertung effizienter ausführen. Terminiert die strikte Auswertung eines Ausdruckes, so terminiert auch die nicht-strikte Auswertung. Hintergrund hiervon ist die Konfluenz-Eigenschaft des jeder funktionalen Sprache zugrundeliegenden λ-Kalküls, die aussagt, dass das Ergebnis der Berechnung nicht von der Reihenfolge der Auswertung abhängt.
Funktionale Algorithmen und Datenstrukturen
[Bearbeiten | Quelltext bearbeiten]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 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 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 (ephemere Datenstrukturen), wohingegen funktionale Datenstrukturen mehrere Versionen verwalten (persistente Datenstrukturen).
Beispiele
[Bearbeiten | Quelltext bearbeiten]Folgende Programme definieren eine Funktion ringarea
, die die Fläche berechnet, die zwischen den beiden konzentrischen Kreisen mit den Radien R
und r
bzw. r1
und r2
mit gemeinsamen Mittelpunkt liegt, entsprechend dem mathematischen Ausdruck: . Dazu werden die Konstante pi
und die Hilfsfunktion sq
definiert. Diese werden von ringarea
dann für die Berechnung benutzt. Anmerkung: Wir setzen voraus, dass gilt.
(defun ringarea (r1 r2)
(flet ((sq (x)
(* x x)))
(* pi (- (sq r1)
(sq r2)))))
F# und OCaml
[Bearbeiten | Quelltext bearbeiten]let ringarea (r1, r2) =
let pi = 3.14 in
let sq x = x*x in
pi * (sq r1 - sq r2)
ringArea :: (Floating a) => a -> a -> a
ringArea r1 r2 = pi * (sq r1 - sq r2) where
sq x = x * x
Der Typ der Funktionen sq, ringArea
ist polymorph und wird durch die Angabe der Typklasse Floating
eingegrenzt. Die explizite Spezifikation des Typs ist optional und kann ebenso gut durch den Haskell-Compiler inferenziert werden. Pi ist in Haskell vordefiniert.
DEFINE
pi == 3.14;
sq == dup *;
ringarea == sq swap sq swap - pi *.
Joy benutzt die umgekehrte polnische Notation. Man beachte, dass hier alle Variablen Funktionen bezeichnen (auch pi ist eine Funktion).
pi = 3.14;
sq(x::Number) = x * x;
ringarea(R::Number, r::Number) = pi * (sq(R) - sq(r));
from math import pi
squared = lambda x: x ** 2
ringarea = lambda r1, r2: pi * (squared(r1) - squared(r2))
squared = @(x) x.^2;
ringarea = @(x,y) pi * (squared(x) - squared(y))
val squared: Double => Double = x => x * x
val ringArea: (Double, Double) => Double =
(outerRadius, innerRadius) =>
math.Pi * (squared(outerRadius) - squared(innerRadius))
val sq: (Double) -> Double = { x -> x * x }
val ringarea = { 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-area r1 r2)
(define pi 3.14)
(define (sq x)
(* x x))
(* pi (- (sq r1)
(sq r2))))
let
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
muss 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.
let pi = 3.14
let square = { (x: Double) -> Double in x * x }
let ringarea = { (r1: Double, r2: Double) -> Double in
pi * (square(r1) - square(r2))
}
XSLT
[Bearbeiten | Quelltext bearbeiten]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:function name="str:reverse" as="xs:string">
<xsl:param name="sentence" as="xs:string"/>
<xsl:choose>
<xsl:when test="contains($sentence, ' ')">
<xsl:sequence select="concat(str:reverse(
substring-after($sentence, ' ')),
' ',
substring-before($sentence, ' '))"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$sentence"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="/">
<output>
<xsl:value-of select="str:reverse('DOG BITES MAN')"/>
</output>
</xsl:template>
Siehe auch
[Bearbeiten | Quelltext bearbeiten]- International Conference on Functional Programming Contest (ICFP Contest)
- Algebraische Programmiersprache
Literatur
[Bearbeiten | Quelltext bearbeiten]- Chris Okasaki: Purely Functional Data Structures. Cambridge University Press, 1999, ISBN 0-521-66350-4.
- Patrick M. Krusenotto Funktionale 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.
Weblinks
[Bearbeiten | Quelltext bearbeiten]- Was ist funktionale Programmierung?, Fakultät für Informatik und Mathematik der Universität Passau
- Amr Sabry: What is a Purely Functional Language? In: J. Functional Programming. 8(1), Januar 1998, S. 1–22.
- Carsten König: Was ist funktionale Programmierung?, Informatik Aktuell (Magazin)
- Andre Schütz: Funktionale Programmierung mit Scala: Einführung, Informatik Aktuell (Magazin)
Einzelnachweise
[Bearbeiten | Quelltext bearbeiten]- ↑ a b Patrick M. Krusenotto: Funktionale Programmierung und Metaprogrammierung. Springer, Wiesbaden 2016, S. 217ff, S. 244 ff.
- ↑ The Functional Programming Language XSLT ( vom 5. Dezember 2008 im Internet Archive)
- ↑ Aus der W3C Recommendation XSL Transformations (XSLT) Version 2.0