Functional programming – Teil 3

Verkettung

Functional programming – Teil 3

Ok. Jetzt haben wir uns einen groben Überblick darüber verschafft wie funktionale Programmierung auf… erm, ja… Funktionsebene funktioniert.
Allerdings haben wir es auch bei diesem Stil mit einer ganzen Reihe von Typen zu tun.
Sehen auf den ersten Blick zwar aus wie Objekte und implementieren auch ein oder mehrere Interfaces. Sie sind aber beileibe nicht das, was man sich im OOP Sinne unter Klassen vorstellen würde.

Fantastic terms and where to find them

Bei den Typen von denen eben die Rede war handelt es sich um sogenannte algebraische Strukturen. Für den normalsterblichen Programmierer würde ich eher von einem generalisierten Interface oder etwas in der Art sprechen wollen. Wie dem auch sei…

Funktionale Programmierung driftet spätestens hier konzeptionell drastisch vom OOP Prinzip ab.
Anstatt ganz konkrete Dinge, Prozesse oder ähnliches zu formulieren (DDD anyone?) wird bei funktioneller Programmierung der verallgemeinernde mathematische Zweig und dessen Terminologie bevorzugt.

Tatsächlich verbergen sich hinter den meisten fremdartigen Begriffen dann aber doch eher einfache, wenngleich mächtige Konzepte. Zumindest für jemanden wie mich, der Mathe im Abi abgewählt hat.

Was mich zu der Feststellung führt, dass der Versuch die FP-Terminologie so weit wie möglich aussen vor zu halten eher vergebens sein wird.
Na gut, dann versuche ich zumindest die Begriffe auf ihren praktischen Nutzen herunter zu brechen.

Functors

Im Grunde nichts anderes als ein Container mit einer map Funktion, der einen beliebigen Wert umschliesst und mir damit direkten Zugriff auf diesen Wert verwehrt.

const Bag = x => ({
  map: fn => Bag(fn(x))
})

Als higher order function erwartet map als parameter eine weitere Funktion welche mit dem Wert innerhalb des Containers aufgerufen wird. Das Ergebnis hiervon wird nun wieder in einen neuen Container gepackt.

Tatsächlich muss ein Functor garantieren, dass map einen neuen Functor zurück liefert.

Dadurch kann ich map beliebig oft aneinander hängen. Genau wie bei compose oder pipe ermöglicht mir das composition.

Wir können das composition Beispiel von Teil eins auch hier verwenden.

Originalbeispiel

const upper     = str => str.toUpperCase()
const ask       = str => `${str}?`
const askLoudly = composeTwo(ask, upper)

askLoudly("will it blend") // WILL IT BLEND? 

Functor Variante

Bag("will it blend").map(upper).map(ask) // Bag('WILL IT BLEND?')

Der Wert in unserem Container ist nun gleichermaßen umgeformt. Aber eben wieder in einem neuen Container, der uns direkten Zugriff auf den Wert verweigert.

In der Regel bietet jeder dieser Container-Typen wenigstens eine Methode an, um den bloßen Wert aus dem Container auszupacken.

const Bag = x => ({
  map : fn => Bag(fn(x)),
  fold: fn => fn(x)
})

Bag("will it blend").map(upper).fold(ask) // 'WILL IT BLEND?'

Mit diesen beiden Methoden können wir das Ergebnis des composition Beispiel genau nachbilden.

Bordmittel

Würde man jetzt behaupten, dass ein JavaScript Array auch ein Functor ist, hätte man völlig Recht damit.

Array.map macht genau das gleiche wie unser Bag.map, abgesehen davon, dass die übergebene Funktion für jedes Element ausgeführt wird.

Am Ende stehen wir aber trotzdem wieder mit einem neuen Array von gleicher länge da.

Praktischer Nutzen

Mit Hilfe von Array.map und Array.filter können wir eine ganze Reihe von imperativen Loops ersetzen.

Angenommen, wir bekommen folgende Datenstruktur geliefert und sollen daraus eine Liste mit ISO-Codes erzeugen.

const countries = [
    { "iso": "yt", "name": "Mayotte" },
    { "iso": "mq", "name": "Martinique" },
    { "iso": "gp", "name": "Guadeloupe" },
    { "iso": "cw", "name": "Curaco" },
    { "iso": "ic", "name": "Canary Islands" }
]

Imperative Variante

let isoCodes = []
for(let i = 0; i < countries.length; i++ ) {
    isoCodes.push(countries[i].iso)
}
// ["yt", "mq", "gp", "cw", "ic"] 
// 101 Zeichen Code

Funktionale Variante

const isoCodes = countries.map(country => country.iso)
// -> ["yt", "mq", "gp", "cw", "ic"] 
// 54 Zeichen Code

Die funktionale Variante ist nicht nur um fast die hälfte kürzer sondern, meiner Ansicht nach, auch deutlich ausdrucksstärker was die eigentliche Geschäftslogik angeht.

Bei der Verkürzung handelt es sich auch nicht um syntactic sugar oder ähnliches. Es ist schlicht weniger aktiver Code.

Für mich ist das ein zusätzlicher Vorteil, den Funktionale Programmierung bietet.
Die gesamte aktive Code-Masse verringert sich erheblich weil wir in der Lage sind einen großen Teil des üblichen „Füllstoffs“ nicht schreiben zu müssen.


 

Keine Kommentare

Deinen Kommentar hinzufügen

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.