Funktionen

Wie schon beim letzten Mal erwähnt möchte ich heute eine kleine, deutschsprachige Serie – sozusagen als Vorbereitung und Teaser für meinen Workshop – rund um funktionale Programmierung starten.

Legen wir los mit dem grundsätzlichen:

Was ist funktionale Programmierung?

Die deutschsprachige Wikipedia1)Wikipedia: Funktionale Programmierung hat dazu folgendes zu sagen:

Funktionale Programmierung ist ein Programmierparadigma, bei dem Programme ausschließlich aus Funktionen bestehen. Dadurch wird bewusst auf die aus der imperativen Programmierung bekannten Nebenwirkungen verzichtet.

Ich persönlich finde den Satz so etwas seltsam und würde grundsätzlich eher zum englischen Pendant raten.

Auf jeden Fall geht es bei der funktionalen Programmierung um Funktionen und das Arbeiten mit diesen!

Was sind Funktionen?

Dumme Frage oder?

Als Programmierer schreiben wir doch ständig Funktionen. Vielleicht nennen wird das Methoden aber so in etwa wissen wir doch was das ist.

Eine erste gedankliche Identifikation mit Methoden (oder statischen Funktionen) ist gar nicht so schlecht! Konzeptionell könnte man eine Funktion als “Eingabe/Argumente => Ausgabe/Rückgabe” beschreiben.

Um gleich ein wenig zu verallgemeinern: das umfasst durchaus auch Prozeduren und Funktionen ohne Argumente – also die Geschichten, die C-ähnliche Sprachen gerne mit void beschreiben. Anstatt void können wir einfach einen Datentyp (meist Unit) genannt einführen, der genau einen Wert annehmen kann (meistens () geschrieben).

In der funktionalen Programmierung möchte man aber etwas mehr. Wir möchten am liebsten Funktionen im mathematischen Sinne haben:

Zu jeder Eingabe \(x\) liefert eine Funktion \(f\) genau eine Ausgaben \(f(x)\)

Und das wirklich streng – eigentlich soll es nicht sein, dass wir von GetCurrentTime () ständig verschiedene Rückgaben bekommen und eine Funktion sollte auch so ehrlich sein und keine Nebeneffekte haben – Console.WriteLine ("Hallo Welt") ist also nicht gerade das beste Beispiel für ein funktionales Programm.

Funktionen und Programmiersprachen, die sich daran halten, nennt man deshalb gerne pure (das deutsche rein klingt irgendwie belastet oder?).

Eigentlich schließt das sogar Funktionen aus, die Ausnahmen werfen (ok wir könnten diese als Teil der Rückgabemenge sehen) oder gar in einer Endlosschleife landen. Allerdings werden die Sprachen, die das garantieren, schon exotischer. Ein schönes Beispiel ist Agda – allerdings zum Preis, dass die Sprache nicht mehr turing-vollständig ist, aber lassen wir das.

Was fangen wir jetzt damit an?

Das eigentlich Wichtige ist nun aber, dass wir mit Funktionen vernünftig arbeiten können. Dahinter steckt nichts weiter als, dass ich in der Programmiersprache eine Funktion selbst wie einen gewöhnlichen Wert behandeln kann. Also insbesondere möchte ich Funktionen in Variablen (eigentlich ein unschönes Wort in der FP – später mehr) speichern als Argumente an andere Funktionen übergeben oder aus solchen zurückbekommen können. Das meint man, wenn man sagt “Funktionen sind first-class” und Funktionen, die andere Funktionen als Argumente erhalten oder zurückgeben nennt man higher-order functions, also auf Deutsch “Funktionen höherer Ordnung”. Übrigens werde ich mich nicht sonderlich bemühen alles einzudeutschen – ich finde das klingt teilweise seltsam. Wir sprechen ja auch von while-Schleifen, commits, sind agile usw.

Glücklicherweise ist in dem Sinne heutzutage praktisch jede relevante Sprache funktional – insbesondere auch C#, VB.net und natürlich auch JavaScript 😉

In C#/VB.net beispielsweise kann man Funktionen in Func<..> Delegaten speichern und umherreichen. Zusammen mit allerlei Hilfsmitteln (wie Lambdas) macht das C# durchaus zu einer ernsthaften funktionalen Sprache und LINQ zelebriert das im großen Stil.

Warum dann überhaupt eine neue funktionale Programmiersprache lernen?

Während wir also durchaus funktional in C# programmieren können, stößt man sich bald an allerlei unschönen Dingen, die mit der Syntax und einigen fehlenden Features von C# zu tun haben.

Hier mal ein Beispiel: Eine der gewöhnlichsten Operationen die man mit Funktionen machen kann ist die Komposition. Wir möchten aus zwei Funktionen \(g: Y \rightarrow Z \) und \(f: X \rightarrow Y\) eine neue machen, die die beiden Funktionen hintereinander ausführt: \(g \circ f: X \rightarrow Z\) (indem wir einfach \(g \circ f (x) = g(f(x))\) setzen).

In C# würde die Operation dementsprechend so aussehen:

Func<X, Z> Komposition<X,Y,Z>(Func<Y, Z> g, Func<X, Y> f)
{
    return x => g(f(x));
}

Func<int,String> g = i => i.ToString();
Func<int,int> f = n => n*n;

var quadriereInString = Komposition(g,f);
var test = quadriereInString(5); // gibt "25"

Das funktioniert natürlich – allerdings würde es wohl kein C# Programmierer jemals einsetzen – es ist unpraktisch.

Alleine das hinschreiben der Func<...> Teile stört auf Dauer einfach (noch schlimmer würde es bei curried Funktionen aussehen – mehr dazu in Zukunft 😉 ) – glaubt mir: das wird so grausam das euch die Kollegen beim Versuch richtig funktional zu werden mit Sicherheit zum Teufel jagen.

Anders sieht es in funktionalen Sprache wie F# oder Haskell aus. Dort ist das Arbeiten mit Funktionen bereits in der Syntax erfasst und natürlich gibt es schon einen Operator (bzw. mehrere) für die Komposition.

In F# hätte wir etwa (der Operator ist << bzw. >>):

let g i = string i
let f n = n*n

let quadriereInString = f >> g // bzw. g << f
let test = quadriereInString 5

oder ganz ähnlich in Haskell (der Operator ist .):

g = show
f n = n * n

quadriereInString = g . f
test = quadriereInString 5

Übrigens: das es diese beiden Sprachen so richtig ernst mit Funktionen nehmen sieht man schon daran, dass der Aufruf einer Funktion (das Auswerten einer Funktion mittel eines Arguments) einfach durch ein Leerzeichen geschrieben wird (quadriereInString 5 VS quadriereInString(5)) und diese Operation hat die höchste Präzedenz2)Operatorrange!

So – dass soll es erstmal gewesen sein!
Natürlich gibt es noch viel mehr zu sagen (und es wird noch mehr gesagt werden) – also seit gespannt!

References   [ + ]