Functional programming – Teil 6

lambda 2 brain

Functional programming – Teil 6

Von den alternativen Kontrollstrukturen, die wir uns letztes mal angesehen haben, war eigentlich nur das Either streng funktional. Dennoch ist es immer von Vorteil den Code so einfach und erweiterbar wie möglich zu gestalten.

Werfen wir diesmal einen Blick darauf, wie wir mit verschachtelten Container-Typen umgehen.

Pointed functors

Hierbei handelt es sich um einen Typen der einen Typkonstruktor anbietet. Im Grunde nichts anderes als eine Factory-Methode, die es uns erlaubt, unseren Wert in einem Container zu platzieren ohne uns Gedanken über eventuelle Komplexität im Konstruktor machen zu müssen.
Dieses Verfahren nennt sich häufig auch lifting im FP Kontext. Die Methode hierfür heisst für Gewöhnlich of.

Either.of('abc') // -> Right('abc')
Task.of('abc')   // -> Task('abc')
Maybe.of('abc')  // -> Just('abc') 

Monads

Sehen wir uns hier für noch mal eines der Beispiele von letztem mal an.

const fromValidation = ({payload, errors}) => errors ? Left(errors) : Right(payload) 

const result = fromValidation(validate(account))
 .map(withdrawMoney)
 .map(notifyAccounting) 
 .fold(onError, onSuccess)

Angenommen dass withdrawMoney ebenfalls eine Prüfung durchführt und ebenfalls ein Either zurück liefert. Damit hätten wir unsere Nutzlast zwei Either tief verschachtelt.
Wir müssen im weiteren Verlauf also zwei mal map bemühen um an den eigentlichen Wert zu gelangen. Ziemlich unhandlich und verwirrend.

const result = fromValidation(validate(account))
 .map(withdrawMoney) // -> Either(Either(account))
 .map(withdrawn => withdrawn.map(notifyAccounting)) // 2x map
 .fold(onError, onSuccess)

Einheitsfunktion

Um diese Verschachtelung von Typen unter Kontrolle zu halten gibt es die sog. Einheitsfunktion. Diese muss in der Lage sein, zwei gleiche Typen auf einen zu reduzieren.

Der konkrete Name dieser Funktion kann durchaus unterschiedlich sein. Gängig sind jedoch chain, join oder flatMap.

Für unser Right sieht das folgendermassen aus

const Right = x => ({
    chain: fn => fn(x),
    map  : fn => Right(fn(x)),
    // ...
})

Anstatt das Ergebnis von fn(x) in ein neues Right zu packen, geben wir einfach das Ergebnis zurück.

Da Left konzeptionell schlicht jegliche Transformation seines Wertes ignorieren soll, sieht das chain hier ein wenig anders aus.

const Left = x ({
    chain: fn => Left(x),
    map  : fn => Left(x)
    // ...
})

Mit dieser Funktion im Arsenal können wir unsere Komposition wieder deutlich vereinfachen.

const result = fromValidation(validate(account))
 .chain(withdrawMoney) // -> Either(account)
 .map(notifyAccounting) // 1x map
 .fold(onError, onSuccess)

Abhängig davon, was uns die Funktionen in unserer Komposition zurückgeben können wir zwischen map und chain wählen.

Wie eingangs erwähnt ist ein Functor der einen Typkonstruktor (of) und eine Einheitsfunktion (chain) besitzt ein monad.
Wie bei den anderen algebraischen Datentypen steckt hier noch etwas mehr mathematische Gesetzmässigkeit dahinter.

Left identity (Linksneutral, Linkseins)

Verwende ich chain auf einer Monade mit einer Funktion die eine gleichartige Monade zurückgibt ist es das gleiche als würde ich besagte Funktion direkt mit dem Wert der Monade ausführen.

const plusOne = x => Right(x + 1)

Right(10).chain(plusOne) ==== fn(a) // -> 11 

Right identity (Rechtsneutral, Rechtseins)

Ein Element in einer Monade welches mittels chain an eine Funktion übergeben wird, die den Wert in eine gleichartigen Monade packt bleibt unverändert.

Right(1).chain(Right) ==== 1 

Assoziativität

Bei einer Reihe von Funktionen, die gegen eine Monade arbeiten muss, bei gleicher Reihenfolge, die Verschachtelung von chain beliebig gewählt werden können.

const plusOne  = x => Right(x + 1)
const timesTwo = x => Right(x * 2)

Right(1).chain(plusOne).chain(timesTwo) ==== Right(1).chain(v => plusOne(v).chain(timesTwo))

Ist wenigstens eines dieser Gesetze nicht erfüllt, dann ist es keine Monade.


 

Keine Kommentare

Deinen Kommentar hinzufügen

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