Functional programming – Teil 2
Alles klar, ein kurzer Rückblick. Wir wissen jetzt was es mit composition auf sich hat und was eine Sprache generell dafür mitbringen muss.
Einen weiteren wichtigen Aspekt des ganzen haben wir allerdings noch nicht besprochen. Eigentlich ist das sogar einer der Eckpfeiler und Hauptgründe der diesen Programmierstil so robust macht.
Pure functions
Eine Funktion wird dann als rein oder pure bezeichnet wenn sie für den selben Input immer das gleiche zurückgibt und sonst keine beobachtbaren Nebeneffekte verursacht.
Nehmen wir zur Veranschaulichung die Array Funktionen slice
und splice
. In beiden Fällen ist die Rückgabe ein Teil des jeweiligen Array.
const fruits = ["Banana", "Orange", "Apple", "Mango", "Kiwi"]
// pure
fruits.slice(0,3) // ["Banana", "Orange", "Apple"]
fruits.slice(0,3) // ["Banana", "Orange", "Apple"]
fruits.slice(0,3) // ["Banana", "Orange", "Apple"]
// impure
fruits.splice(0,3) // ["Banana", "Orange", "Apple"]
fruits.splice(0,3) // ["Mango", "Kiwi"]
fruits.splice(0,3) // []
Die Funktion splice
disqualifiziert sich als reine Funktion da sie für den selben Input nicht immer genau das gleiche liefert und einen beobachtbaren Nebeneffekt verursacht.
Beides deshalb weil sie bei jedem Aufruf den (beobachtbaren) Zustand des Array verändert.
Was ist daran jetzt so schlimm?
Die Zustandsveränderung muss im weiteren Programmverlauf berücksichtigt werden. Dadurch erhöht sich die Komplexität des Gesamtsystems. Damit erhöht sich auch die kognitive Belastung.
let config = {
language: "de"
}
const baseUrl = id =>
`https://${config.language}.content.api/${id}`
In diesem Beispiel verlässt sich baseUrl
auf den veränderlichen Zustand von config
. Es ist nicht garantiert, dass config.language
im Programmverlauf nicht plötzlich einen anderen Wert besitzt.
Damit ist auch nicht garantiert, dass baseUrl
für den selben Input immer das selbe Ergebnis liefert.
Ich muss also zu jeder Zeit im Kopf behalten welche anderen Programmteile auf config zugreifen.
Aus dem Nähkästchen
Ich kann mich noch bestens daran erinnern einen ähnlichen Bug aufspüren zu dürfen:
Irgendwo in den Tiefen des Systems sorgte eine Komponente dafür, dass die eingestellte Sprache auf eine andere umgestellt wurde. In bester Absicht, wohlgemerkt.
Unter gewissen Umständen sorgte das wiederum dafür, dass gemischt sprachige Inhalte geliefert wurden.
Natürlich mussten wir zuerst herausfinden wie wir eben diese Umstände reproduzieren können um nachverfolgen zu können wann und wodurch die Zustandsveränderung herbeigeführt wurde.
Eine echte Gaudi, die konkrete Ursache zu finden.
Um genau solche Situationen zu minimieren wird bei funktionaler Programmierung großer Wert auf reine Funktionen gelegt, die völlig autark sind und absolut zuverlässige Rückgaben produzieren ohne Spuren zu hinterlassen.
Weitere Nebeneffekte
Ein Nebeneffekt ist eine Veränderung des Systemzustands oder eine beobachtbare Interaktion mit der Aussenwelt die während der Berechnung eines Ergebnisses auftritt.
Das ist so ziemlich die treffendste Definition, die ich finden konnte. Die Liste von möglichen Nebeneffekten wird damit jedoch beliebig lang und umfasst eine ganze Reihe von notwendigen Operationen wie:
- Benutzereingaben abholen
- Bildschirmausgaben
- Logging
- HTTP-Requests
- In eine Datenbank schreiben
- Das Dateisystem verändern
Diese Aufzählung lässt sich beliebig verlängern und dient nur der groben Orientierung.
Stellt sich jetzt natürlich die Frage: „Wie schreibt man denn bitte ein Programm ohne Nebeneffekte?“
Die schlichte Antwort darauf: Gar nicht.
Funktionelle Programmierung verbietet keine Nebeneffekte. Vielmehr wird gefordert, dass besagte Nebeneffekte auf eine Kontrollierte und sehr begrenzte Weise ablaufen.
In der Praxis bedeutet das nicht selten, dass eine Funktion die einen Nebeneffekt verursachen würde lediglich vorbereitet jedoch nicht ausgelöst wird.
Die Bürde den Nebeneffekt auszulösen wird damit an denjenigen übertragen, der die Funktion aufruft.
Keine Kommentare