Reine Funktionen sind mathematischen Funktionen sehr ähnlich. Sie sind der Grund dafür, das Haskell als rein funktionale Programmiersprache bezeichnet wird.
Reine Funktionen
In der folgenden Tabelle stellte ich reine den unreinen Funktionen gegenüber.
Reine Funktionen besitzen einen entscheidenden Nachteil. Sie können nicht mit der Welt interagieren. Den Funktionen zur Ein- und Ausgabe, Funktionen, die Zustand aufbauen oder auch Funktionen, die Zufallszahlen ermitteln, können nicht rein sein. Den einzigen Effekt, den reine Funktionen laut Simon Peyton Jones besitzen können, ist, den Raum zu wärmen. Haskell löst sich aus dieser Sackgasse, in dem sie unreine, imperative Subsysteme in die rein funktionale Programmiersprache einbettet. Die imperativen Subsysteme werden in Haskell Monaden genannt. Dazu gleich mehr.
Wie steht es nun um die Reinheit von C++? Diese basiert ähnlich wie der Umgang mit unveränderlichen Daten auf der Disziplin des Programmierers. Das folgende Programm stelle ich eine Funktion, eine Meta-Funktion und eine constexpr-Funktion als reine Funktion vor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
// pureFunctions.cpp
#include <iostream>
int powFunc(int m, int n){
if (n == 0) return 1;
return m * powFunc(m, n-1);
}
template<int m, int n>
struct PowMeta{
static int const value = m * PowMeta<m,n-1>::value;
};
template<int m>
struct PowMeta<m,0>{
static int const value = 1;
};
constexpr int powConst(int m, int n){
int r = 1;
for(int k=1; k<=n; ++k) r*= m;
return r;
}
int main(){
std::cout << powFunc(2,10) << std::endl; // 1024
std::cout << PowMeta<2,10>::value << std::endl; // 1024
std::cout << powConst(2,10) << std::endl; // 1024
}
|
Auch wenn die drei Funktionen das gleiche Ergebnis liefern, sind sie doch sehr verschieden. powFunc (Zeile 5 - 8) ist eine klassische Funktion. Sie wird zur Laufzeit des Programms ausgeführt und kann auch mit nicht-konstanten Ausdrücken umgehen. Im Gegensatz dazu ist PowMeta (Zeile 10 - 18) eine Meta-Funktion, die zur Übersetzungszeit des Programms ausgeführt wird. Daher benötigt sie konstante Ausdrücke als Argumente. Die constexpr-Funktion powConst (Zeile 20 - 24) kann zur Übersetzungszeit und zur Laufzeit des Programms ausgeführt werden. Wird sie zur Übersetzungszeit des Programms ausgeführt, benötigt sie wie die Meta-Funktion PowMeta konstante Ausdrücke als Argumente.
An now to something complitly different. Im nächsten Abschnitt stelle ich in einer ersten Annäherung Monaden in Haskell vor. Diese Annäherung werde ich im Laufe der weiteren Artikel zur funktionalen Programmierung in C++ sukzessive verfeinern. Jetzt rede ich aber über die Zukunft, denn mit std::optional in C++17, der Ranges-Library von Eric Niebler und den Erweiterungen von std::future stehen uns in C++ eine monadische Zukunft bevor. Oder, wie es Bartosz Milewski in seinem absolut sehenswerten Vortrag so treffend formuliert hat: I See a Monad in Your Future.
Monaden
Haskell als rein funktionale Sprache verwendet ausschließlich reine Funktionen. Diese reinen Funktionen zeichnen sich insbesondere dadurch aus, dass sie immer das gleiche Ergebnis zurückgeben, wenn sie mit den gleichen Argumenten aufgerufen werden. Dank dieser Eigenschaft, referenzielle Transparenz genannt, ist es für Funktionen in Haskell nicht möglich, Seiteneffekte zu besitzen. Damit besitzt Haskell ein konzeptionelles Problem. Die Welt ist voll von Berechnungen, die Seiteneffekte besitzen. Dies sind Berechnungen, die fehl schlagen, eine nicht deterministische Anzahl von Ergebnissen erzeugen oder auch von der Umgebung abhängen. Um dieses konzeptionelle Problem zu lösen, führte Haskell Monaden ein und bettete diese in die rein funktionale Programmiersprache ein.
Die klassischen Monaden kapseln genau einen Seiteneffekt:
- I/O Monade: Berechnungen, die mit der Ein- und Ausgabe interagieren.
- Maybe Monade: Berechnungen, die eventuell einen Wert zurückgeben.
- Error Monade: Berechnungen, die fehlschlagen können.
- List Monade: Berechnungen, die beliebige viele Ergebnisse besitzen können .
- State Monade: Berechnungen, die einen Zustand aufbauen.
- Reader Monade: Berechnungen, die Werte aus der Umgebung lesen.
- Writer Monade: Berechnungen, die einen Strom von Daten ergeben.
Das Konzept der Monaden stammt aus der Kategorientheorie, einem Zweig der Mathematik, der Objekte und die Abbildungen auf diesen Objekten beschreibt. Monaden sind abstrakte Datentypen (Typklassen), die einfache Datentypen in höhere Datentypen transformieren. Werte dieser höheren Datentypen werden monadische Werte genannt. Einmal in der Monade gefangen, kann der monadische Wert durch Funktionskomposition nur in einen anderen monadischen Wert transformiert werden.
Diese Transformation berücksichtigt die besondere Struktur der Monade. So bricht die Error Monade bei einem Fehler die Berechnung vorzeitig ab, so baut die State Monade auf ihrem bestehenden Zustand auf oder akkumuliert die Writer Monade ihre Logdatei.
Damit dies möglich ist, bestehen Monaden formal gesprochen aus drei Komponenten:
- Typkonstruktor: Dieser erklärt für den zugrunde liegenden einfachen Typ, wie der entsprechende monadische Typ zu erhalten ist.
- Funktionen:
- Einheitsfunktion: Führt die einfachen Werte in die Monade ein.
- bind Operator: Definiert, wie eine Funktion auf einem monadischen Wert angewandt wird, um einen neuen monadischen Wert zu erhalten.
- Regeln für die Funktionen:
- Die Einheitsfunktion muss das links- und rechts neutrale Element sein.
- Die Komposition von Funktionen muss assoziativ sein.
Wird ein Typ wie die Error Monade Instanz der Monade Typklasse, muss sie die Einheitsfunktion und den bind Operator implementieren. Diese zwei Funktionen definieren explizit, wie die Error Monade mit Fehlern in Berechnungen umgeht. Beim Einsatz der Error Monade tritt die Fehlerbehandlung in den Hintergrund.
Eine Monade besteht aus zwei Kontrollflüssen. Dem regulären, expliziten Kontrollfluss, der den Wert berechnet und dem impliziten Kontrollfluss, der sich um ihren speziellen Seiteneffekt kümmert.
Wie geht's weiter?
Rein funktionale Sprachen kennen keine veränderlichen Daten. Daher verwenden sie Anstelle von Schleifen Rekursion. Wie, das genau zeigt der nächste Artikel.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...