arrow-left

Only this pageAll pages
gitbookPowered by GitBook
1 of 14

Lambda Kalkül für Javascript

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Immutable Stack

hashtag
Beschreibung

hashtag
Stack

Der Stack ist eine rein funktionale Datenstruktur und daher unveränderlich. Der Stack ist als implementiert. Ein Tripel ist eine weitere rein funktionale Datenstruktur, die drei Werte hält. Über "getter"-Funktionen kann auf diese Werte des Tripels zugegriffen werden. Der erste Wert des Tripels stellt die Größe (Anzahl der Elemente) des Stacks dar. Gleichzeitig repräsentiert der erste Wert, den Index des Kopfes (oberster Wert), des Stacks. Die Grösse/der Index, des Stacks wird als angegeben. Der zweite Wert repräsentiert den Vorgänger-Stack. Der dritte Wert stellt den Kopf ( oberster Wert ) des Stacks dar.

Stack Implementation:

hashtag
Empty-Stack

Zur späteren Verwendung von einem Stack wird der leere Stack als Grundbaustein benötigt. Der leere Stack hat die Grösse/ den Index Null. Der leere Stack hat keinen Vorgänger, stattdessen hat er die als Platzhalter. Ausserdem bestitzt der leere Stack keinen Kopf (oberster Wert), sondern hat als Platzhalter die Identitätsfunktion.

Implementation des leeren Stacks:

hashtag
Aufbau

Ein kleines grafisches Beispiel wie ein Stack aussieht. In diesem Beispiel wird ein Stack mit Emoji's erstellt:

stack-name
stack
code

s3 = (n3)( (n2)( (n1)( (n0)(id)(id) )(😎) )(🤓) )(👾)

Der Stack s3 besteht nun aus den Elementen: 😎, 🤓, 👾 .

  • Element an Index 1:😎

  • Element an Index 2:🤓

  • Element an Index 3:👾

hashtag
Verwendung

hashtag
push

Um einen Stack zu erstellen fügt man Elemente, dem leeren Stack hinzu. Dafür gibt es die Push-Funktion. Die Push-Funktion nimmt einen Stack und einen Wert entgegen. Der übergebene Wert, wird auf den übergegebenen Stack hinzugefügt.

Beispiel:

Nun besitzt der Stack von oben den Wert 1.

hashtag
pop

Um den obersten Wert vom Stack zu entfernen gibt es die pop-Funktion. Die pop-Funktion gibt ein zurück. Dieses Pair besteht aus dem vorgänger-Stack und dem Wert, der vom Stack entfernt wurde. Mit den "getter"-Funktionen für Pairs, kann auf die Werte zugegriffen werden.

Beispiel:

hashtag
Weitere Funktionen

hashtag
size

Um auf den auf die Grösse eines Stacks zuzugreifen gibt es die Funktion size. Diese Funktion nimmt einen Stack entgegen und gibt die Grösse, des Stacks als zurück.

Beispiel:

hashtag
head

Um auf den Kopf (oberster Wert) des Stacks zuzugreifen gibt es die Funktion head. Diese Funktion nimmt ein Stack entgegen und gibt den Kopf des Stacks zurück.

Beispiel:

hashtag
hasPre

Die Funktion hasPre nimmt einen Stack entgegen und gibt ein zurück, der aussagt ob der übergegebene Stack einen Vorgänger hat oder nicht.

Beispiel:

hashtag
Element per Index holen

hashtag
getElementByIndex

Die Funktion getElementByIndex nimmt einen Stack und eine oder JS-Zahl, die den Index des Elements repräsentiert, entgegen. Falls an diesem Index ein Element existiert, wird dieses zurückgegeben ansonsten wird auf der Console einer Error-Hinweis erscheinen.

Beispiel:

hashtag
Stack zu einem Array konvertieren und umgekehrt

hashtag
convertStackToArray

Die Funktion convertStackToArray nimmt einen Stack entgegen und gibt einen Array mit denselben Elementen zurück.

Beispiel:

hashtag
convertArrayToStack

Die Funktion convertArrayToStack nimmt einen Array entgegen und gibt einen neuen Stack mit den Elementenn vom übergebenen Array zurück.

Beispiel:

hashtag
Stack umkehren

hashtag
reverseStack

Die Funktion reverseStack nimmt einen Stack entgegen und gibt einen neuen Stack zurück, bei diesem die Elemente in umgekehrter Reihenfolge sind.

Beispiel:

hashtag
Stack - Reduce, Map und Filter

Die JavaScript Funktionen reduce, map und filter wurden auch für den Stack implementiert.

hashtag
Reduce

Reduce nimmt einen Stack entgegen und ein Argument-. Das erste Argument des Paares muss eine reduce-Funktion(wie bei JavaScript reduce). Das zweite Argument muss ein Startwert sein. Die Funktion gibt den reduzierten Wert zurück.

Beispiel:

hashtag
Map

Map nimmt einen Stack und eine map-Funktion (wie bei JavaScript Array map) entgegen. Zurück gibt die Funktion einen neuen Stack mit den "gemappten" Werten.

Beispiel:

hashtag
Map with Reduce

Ausserdem gibt es noch eine MapWithReduce-Funktion die mittels der obenstehenden reduce-Funktion implementiert ist. Sie nimmt auch einen Stack und eine Map-Funktion entgegen. Diese Funktion kann genau gleich wie die Map Funktion verwendet werden.

Implementation:

hashtag
Filter

Filter nimmt einen Stack und eine filter-Funktion (wie bei JavaScript Array filter) entgegen. Die Funktion gibt den gefilterten Stack zurück. Wenn keine Elemente dem Filter entsprechen wird der leere Stack zurückgegeben.

Beispiel:

hashtag
Filter with Reduce

Ausserdem gibt es noch eine FilterWithReduce-Funktion die mittels der obenstehenden reduce-Funktion implementiert ist. Sie nimmt auch einen Stack und eine Filter-Funktion entgegen. Diese Funktion kann genau gleich wie die Filter Funktion verwendet werden.

Implementation:

hashtag
ForEach-Loop

Die Funktion forEach nimmt einen Stack und eine Callback-Funktion entgegen. Die Funktion iteriert über den Stack und ruft in jeder Iteration die Callbackfunktion auf. Der Callbackfunktion werden zwei Argumente übergeben. Das erste Argument ist das Element von der aktuellen Iterationsrunde. Das zweite Argument ist der Index, des Elements.

Beispiel:

Bei der Implementierung von der forEach-Funktion wurde für die eigentliche Iteration verwendet.

circle-info

Die forEach-Funktion für Stacks funktioniert gleich wie die JavaScript forEach Schlaufe.

hashtag
Nützliche Helferfunktionen

hashtag
Stack auf der Konsole ausgeben - logStackToConsole

Die Funktion logStackToConsole nimmt einen Stack entgegen und führt einen Seiteneffekt aus. Der Seiteneffekt loggt den Stack auf die JavaScript-Konsole.

Beispiel:

hashtag
Stack erstellen mit Helferfunktion - startStack

Die pushToStack Funktion wird der startStack Funktion übergeben. Danach folgt der erste Wert, der hinzugefügt werden soll. Für weitere Werte kann nochmals die pushToStack Funktion und ein weiteres Element hinzugefügt werden. Dies kann solange gemacht werden, wie man möchte. Um das Erstellen abzuschliessen, wird am Schluss die übergeben.

Durch diese Helferfunktion lassen sich Stacks bequemer erstellen.

hashtag
Eigenschaften der Funktionen vom Stack

  • Alle Funktionen sind rein (mit Ausnahme logStackToConsole).

  • In allen Funktionen gibt es keine Ausdrücke wie for, while oder do Schleifen.

s2

(n2)(s1)( 🤓 )

const s3 = push(s2)( 👾 );

s3

(n3)(s2)( 👾 )

Die Iteration ist mit church-Zahlen implementiert.

emptyStack

(n0)(id)(id)

const s1 = push(empyStack)( 😎 );

s1

(n1)(emptyStack)( 😎 )

const s2 = push(s1)( 🤓 );

Tripel
Church-Zahl
Identitätsfunktion
Pair
Church-Zahl
Church-Boolean
Church-
Pair
Church-Zahlen
Identitätsfunktion

Der lambdafizierter Taschenrechner

hashtag
Beschreibung

Ein Einstieg-Projekt, um sich am beste mit den Kombinatoren und den Church-Zahlen auseinander zusetzen, ist Taschenrechner daraus zu bauen.

hashtag
Link zum lambdafizierten Taschenrechner:

hashtag
Kern des lambdafizierten Taschenrechner

Eine Verkettung der arithmetischen Zahlen und Operationen.

Als Helferfunktion gibt es einen sogenannter CalculatorHandler, mit dem solch eine Verkettung von Konstruktion ermöglichen.

Der calculatorHandler nimmt jeweils eine arithmetische Operation (Addition, Subtraktion, Multiplikation usw.), zwei Werte und zum Schluss eine Funktion entgegen.

hashtag
Rechnen mit Zahlen

Um mit Zahlen zu rechnen reichen die Arithmetischen-Operatoren (+, -, * etc.) von JavaScript:

Mit dem CalculatorHandler und den Arithmetischen-Operatoren kombiniert, ist es möglich via Point-Freestyle aus den vorhing erstellten Operatoren neue Funktionen zur Berechnung zu erstellen:

hashtag
Anwendung der Operator-Funktionen

Mit der (T = x => f => f(x) ) als den Taschenrechner-Starter und den neuen Operator-Funktionen, ist es mögliche eine unendliche Verkettungen von Zahlen und Operationen zu erstellen.

Um dieser Verkettung ein Ende zu setzen und das Resultat der Berechnung zu erhalten, benötigt es jeglich die als Letztes anzuwenden.

Um die Leserlichkeit des Code zu verbessern, wird für die Trush- und id-Funktion ein passender Variablename gewählt.

Implementation (Umbenennung):

Beispiel:

hashtag
Rechnen mit Church Encodings-Zahlen

Das der Taschenrechner nicht nur mit JavaScript-Zahlen sondern auch mit rechnen kann, braucht es nur die mit dem CalculatorHandler zu kombinieren.

Mit diesen und den lässt sich der Taschenrechner gleich bequem bedienen.

hashtag
Die Probleme mit den Church-Zahlen

hashtag
Church-Zahlen encoden

Chuch-Zahlen sind Nested-Funktionen und es ist schwer mit blossem Auge zu entziffern welche Zahl sich hinter versteckt. Schon nur die Church-Zahl n7 gibt dir diese Funktion:

Die Hilfe zum die Zahl hinter einer Church-Zahl zu Entziffern ist die Funktion

hashtag
Negative Zahle

Was der lambdafizierter Taschenrechner im vergleich zum JavaScript-Taschenrechner nicht kann sind mit negative Zahlen rechnen, da Church-Zahlen nur Werte der Natürlichen-Zahlen repräsentiert werden kann:

hashtag
Division

Gleiches Problem wie mit den negativen Zahlen, können die Church-Zahlen keine Rationale-Zahlen repräsentiere. Deswegen gibt es keinen lambdafizierten Division-Operator.

hashtag
Maximum call stack size exceeded

Bei Berechnung mit grösseren Church-Zahlen und längerer Verkettungen kann es zu einem Maximum call stack size exceeded - Error kommen:

hashtag
Taschenrechner User-Interface

Um den lambdafizierten Taschenrechner, wie ein gewöhnter Taschenrechner auch visuell bedienen zu können, wurde eine statische HTML-Webseite, mit einem grafischen Taschenrechner und den von hier gezeigten Funktionen implementiert:

Link zum lambdafizierten Taschenrechner:

hashtag
Eigenschaften des lambdafizierter Taschenrechner

  • Alle Funktionen sind rein

  • In allen Funktionen gibt es keine Ausdrücke wie for, while oder do Schleifen.

Immutable ListMap

Stack mit Schlüssel-Wert Paare

hashtag
Beschreibung

circle-info

Die Titel der Funktionen sind mit einem Link zur Implementation verknüpft.

hashtag

ListMap ist eine weitere unveränderliche Datenstruktur, die auf dem Stack aufbaut. Im Kern ist die ListMap Datenstruktur gleich wie der , d.h. sie ist auch als implementiert. Der Unterschied zum Stack ist, dass in der ListMap die Einträge Schlüssel-Wert Paare sind (wie bei einer ). Alle Werte werden in dieser Datenstruktur mit einem dazugehörigen Schlüssel abgespeichert, somit kann der Anwender einen Wert abfragen mit Hilfe des dazugehörigen Schlüssels. Alle Funktionen vom Stack sind kompatibel mit der ListMap, zusätzlich gibt es noch weitere Funktionen, die nur mit einer ListMap verwendet werden können.

hashtag

Die emptyListMap repräsentiert die leere ListMap. Anhand dieser Konstruktion ist zu sehen, dass sie sich nur in einem Punkt zum Stack unterscheidet. Der letzte Parameter, der ListMap ist nicht nur idwie beim Stack, sondern ein Paar mit id als Schlüssel und id als dazugehörigen Wert.

hashtag
Verwendung

Alle Funktionen vom Stack können auch für die ListMap verwendet werden. Hier folgt die Auflistung der zusätzlichen Funktionalität, die nur mit der ListMap kompatibel ist.

circle-info

In den folgenden Beispielen wird zur besseren Übersicht, die ListMap Datenstruktur wie folgt dargestellt: ``[ (key1, value1), (key2, value2), (key3, value3), ... ]

triangle-exclamation

Bei der Verwendung von Funktionen, des Stacks mit der ListMap muss beachtet werden, dass die Elemente immer Schlüssel-Wert Paare sind und somit immer mit einem pair gearbeitet wird als Eintrag.

hashtag

Mit der getElementByKey Funktion kann anhand eines Schlüssels auf den dazugehörigen Wert zugegriffen werden.

hashtag

Mit der Funktion removeByKey kann ein Wert anhand des Schlüssel entfernt werden.

hashtag

Mit der Funktion convertObjToListMap kann ein JavaScript Objekt zu einer ListMap konvertiert werden. JavaScript-Objekte sind Container für benannte Werte, die Properties oder Methoden genannt werden. In der Konvertierungsfunktion werden die Namen als String-Schlüssel verwendet.

Tuple-Konstruktor mit convertObjToListMap

Mit der Funktion convertObjToListMap kann eine Tuple-Artige Datenstruktur mit Zugriffsfunktionen erstellt werden.

Die Funktion personCtor bildet den Konstruktor für das Personen Tuple.

Die übergebenen Variablen im "Konstruktor" bilden später zusammen mit der Funktion getElementByKey die Zugriffsfunktionen für die Werte im Tuple.

hashtag

Mit der Funktion convertListMapToArray kann eine ListMap in ein JavaScript-Array konvertiert werden. Dabei werden nur die Werte in der ListMap erfasst.

hashtag
Higher Order Functions (HOF's) speziell für ListMap

Für die ListMap wurde eine spezifischere Variante für die HOF's map, filter und reduce implementiert. Dies um die Anwendung nochmals zu vereinfachen, weil sonst mit einem pair(key)(value) gearbeitet werden muss, obwohl der Anwender den Key dabei nicht benötigt bzw. verändern darf. Der Key wird in den HOF's für die ListMap weg abstrahiert, sodass sicher der Anwender auf das eigentliche Element konzentrieren kann.

hashtag

Diese Funktion nimmt eine map-Funktion (wie bei JavaScript Array map) und eine ListMap entgegen. Zurück gibt die Funktion eine neue ListMap mit den "gemappten" Werten.

circle-info

Beim Mapping des Wertes bleibt der dazugehörige Schlüssel unverändert.

hashtag

Diese Funktion nimmt eine filter-Funktion (wie bei JavaScript Array filter) und eine ListMap __entgegen. Die Funktion gibt die gefilterte ListMap __zurück. Wenn keine Elemente dem Filter entsprechen wird die leere ListMap __() zurückgegeben.

hashtag

Diese Funktion nimmt als ersten Parameter eine reduce-Funktion entgegen (wie bei JavaScript Array reduce), als zweites einen Startwert und als letzten Parameter eine ListMap. Die Funktion gibt den reduzierten Wert zurück.

hashtag
Helferfunktion

hashtag

Die Funktion logListMapToConsole nimmt eine ListMap entgegen und führt einen Seiteneffekt aus. Der Seiteneffekt gibt die ListMap mit dessen Schlüssel-Wert Paaren auf die JavaScript-Konsole aus.

hashtag
Enstehung der ListMap

Beim ersten Entwurf des Observables wurde für die Verwaltung der Listener die Stack Datenstruktur verwendet. Bei der Implementierung für das abmelden/entfernen der Listener wurde klar das dies mit einem Stack nicht bzw. nicht elegant gelöst werden kann. Dabei kam die Idee einer HashMap auf um einen Listener per Schlüssel abzuspeichern und wieder zu entfernen. Das Problem einer HashMap ist das dies ein gute Hash-Funktion voraussetzt und die ist ein bekanntlich schweres Problem in der Informatik. Auch für den direkten Zugriff auf eine HashMap (in O(1) ) wussten wir nicht wie wir dies implementieren könnten. Da kam uns die Idee das wir eine Liste mit Schlüssel-Wert Paaren entwicklen können ohne diese zu Hashen und den Zugriff auf die Elemente mittels Iteration zum implementieren. Der Schlüssel sollte eindeutig und mit dem JavaScript === Operator auf Gleichheit verglichen werden können. Eine alternative Implementierung wäre eine Art Binär Baum, dies wäre aber sehr komplex und nicht nötig für unsere Einsatz Zwecke. Der Vorteil von unserer Implementierung ist, dass wir den bereits existierenden Stack verwenden und erweitern diesen.

Either

Entweder Erfolgsfall mit Resultat oder Fehlerfall mit Fehlermeldung

hashtag
Beschreibung

hashtag
Either Type

Der Either Type wird häufig in funktionalen Programmiersprachen wie zum Beispiel Haskell oder Scala eingesetzt für das Error Handling. Der Either Type ist ein polymorpher Typ, der zwei Zustände annehmen kann. Für diese zwei Zustände gibt es die Wert-Konstruktoren Left und Right. Somit ist ein Either entweder ein Left oder ein Right. Beide tragen einen Wert mit sich: Left wird verwendet um im Fehlerfall die Fehlermeldung zu kapseln; Right wird verwendet, um im Erfolgsfall den korrekten Wert zu kapseln. Durch den Either Type kann so in rein funktionalen Sprache elegant auf Fehler reagiert werden. Dabei gibt es keine Seiteneffekte, wie es ansonsten mit dem Statement in JavaScript geben würde.

hashtag
Either Type Implementation:

Left und Right sind zwei Funktionen die jeweils einen Wert und zwei Funktionen entgegen nehmen. Beide Funktionen ignorieren eine der beiden übergebenen Funktionen.Left wendet die linke (erste übergebene) Funktion auf den Parameter x an und ignoriert die zweite. Right wendet die rechte (zweite übergebene) Funktion auf den Parameter x an und ignoriert die erste. Left und Right bilden die Basis für einen weiteren Typ, den .

hashtag
Verwendung

circle-info

Die Titel der Funktionen sind mit einem Link zur Implementation verknüpft.

Die folgenden Funktionen geben alle ein Either zurück und unterstützen so eine Fehlerbehandlung mit reinen Funktionen ohne Seiteneffekte. Somit können typische Fehler, die zum Beispiel auftreten wenn Werte null oder undefined sind, vermieden werden. Eine Funktion die ein Either zurück liefert hilft dem Anwender an den Fehlerfall zu denken und diesen zu behandeln.

hashtag
Allgemeine Anwendung für Funktionen, die ein Either zurückgeben

Bei Funktionen, die ein Either zurückgeben können an den Funktionsaufruf zwei weitere Parameter übergeben werden. Der erste Parameter ist eine Funktion, die eine Fehlermeldung entgegen nimmt und dann eine Fehlerbehandlung durchführt. Der zweite Parameter ist eine Funktion für den Erfolgsfall, die das Resultat entgegen nimmt.

Allgemeines Schema:

Eine Either Funktion XYZ wird mit einem oder mehreren Parametern aufgerufen. Am Schluss vom Funktionsaufruf werden 2 Funktionen übergeben. Eine Funktion für den Fehlerfall (Left Case) und eine für den Erfolgsfall (Right Case).

hashtag

Die eitherTruthy Funktion erwartet einen Wert und überprüft ob dieser 'truthy' ist. Im Erfolgsfall wird ein Right mit dem Element zurück gegeben und im Fehlerfall ein Left mit der entsprechenden Fehlermeldung.

circle-info

.

hashtag

Die eitherNotNullAndUndefined ****Funktion erwartet einen Wert und überprüft ob dieser nicht null oder undefined ist.

hashtag

Die eitherElementOrCustomErrorMessage Funktion erwartet eine Fehlermeldung und ein Element. Die Funktion überprüft das Element auf null oder undefined und gibt entweder ein Right mit dem Wert oder ein Left mit der übergebenen Fehlermeldung zurück.

hashtag

Die eitherDomElement Funktion nimmt eine Id für ein Dom-Element entgegen und gibt ein Either Type zurück. Im Erfolgsfall wird das HTML-Element zurückgegeben sonst eine Fehlermeldung, dass ein solches Element nicht existiert.

hashtag

Die eitherNumber Funktion überprüft ob ein Wert vom Typ Integer ist.

hashtag

Die eitherNaturalNumber Funktion überprüft ob der übergebene Wert eine natürliche JavaScript-Zahl ist.

hashtag

Die eitherFunction Funktion überprüft ob ein Wert vom Typ function ist.

hashtag

Die eitherTryCatch Funktion nimmt eine Funktion f entgegen, die schief gehen könnte. Diese Funktion wird in einem try-catch Block ausgeführt. Wenn ein Fehler auftritt während der Funktionsausführung wird dieser gefangen und es wird ein Left mit der Fehlermeldung zurückgegeben, ansonsten ein Right mit dem Resultat.

circle-info

Diese Funktion hat den Zweck bestehende JavaScript Funktionen die noch auf die nicht funktionale Art Fehler mit throw werfen abzufangen und diese in die Welt der funktionalen Programmierung einzubetten. Somit fungiert diese Funktion als Brücke von der JavaScript Welt in die Welt der funktionalen Programmiersprachen.

hashtag

Die Funktion eitherElementsOrErrorsByFunction nimmt als ersten Parameter eine Funktion und als zweiten Parameter einen Rest Parameter (). Die Funktion die übergeben wird sollte einen Wert entgegen nehmen und ein Either Type zurückgeben. Die Funktion eitherElementsOrErrorsByFunction wendet dann die übergebene Funktion auf jeden Wert an der durch den Rest Parameter übergeben wurde. Zurück kommt ein Either. Im Erfolgsfall (Right) bekommt der Anwender eine ListMap mit allen "Erfolgs" -Werten. Im Fehlerfall bekommt der Anwender ein Stack mit allen Fehlermeldungen die aufgetreten sind.

circle-info

Sobald ein Funktionsaufruf schief geht, wird ein Left mit den Fehlermeldungen zurückgegeben.

circle-info

In Haskell hätte diese Funktion folgenden Typ:

Beispiel

Observable

Wie lässt sich ein Wert nach dessen Änderung z.B. auf mehreren Textfeldern synchronisiert darstellen?

In vielen Programmiersprachen bietet sich hierfür das Entwurfsmuster 'Observer-Pattern' an, dass in verschiedenen Sprachen sehr unterschiedlich implementiert wurde. Das Prinzip gestaltet sich allerdings gleich: Der 'Erzähler' (Observable) hält Informationen bereit an die sich 'Zuhörer' (Listener) registrieren können. Sobald der 'Erzähler' neue Informationen bekommt, benachrichtigt er seine 'Zuhörer'.

hashtag
Beispiel

Lambda Kalkül für praktisches JavaScript

Benjamin Brodwolf & Pascal Andermatt

hashtag
Was ist Lambda Kalkül?

Lambda Kalkül ist ein formales System, in der mathematische Logik zur Berechnung und Untersuchung von Funktionen gilt. Es ist ein universelles Berechnungsmodel , mit dem jede Turing-Maschine simuliert werden kann. Es wurde von dem Mathematiker in den 1930er Jahren als Teil seiner Forschung zu den Grundlagen der Mathematik eingeführt.

Box

Verpacken -> Verarbeiten -> Auspacken

hashtag
Beschreibung

Das Box Konstrukt erleichtert das Verarbeiten von beliebigen Werten. Die Werte werden in eine "Box" eingepackt und danach gemapped (weiterverarbeitet). Dabei entsteht eine Art linearer Datenfluss, der die Leserlichkeit des Codes erhöht. Ausserdem werden keine Variablen-Deklarationen für die Zwischenstände benötigt, weil das Resultat der Verarbeitung direkt in die nächste Funktion weitergeleitet wird.

Mit dem Box Konstrukt kann eine Art Pipeline aufgebaut werden, bei dem ein Wert durch diese Pipeline geschickt wird und bei jedem fmap wird der Wert weiter prozessiert. Um am Schluss an den verarbeiteten Wert zu kommen wird die letzte Prozessierung nicht mit

Church Encodings - Booleans und Zahlen

hashtag
Beschreibung

Nebst den bekannten gibt es noch die Church-Booleans und Church-Zahlen. Mit den Church-Booleans werden boolesche Logik mit Funktionen ausgedrückt und die Church-Zahlen sind die bekannteste Form, mit welche die natürlichen Zahlen repräsentiert werden. Benannt sind sie nach , Mathematiker und einer der Begründer der theoretischen Informatik.

const stack = x => y => z => f => f(x)(y)(z);
const emptyStack = stack(n0)(id)(id);
const stackWithOneValue = push(emptyStack)(1);
const resultPair = pop(stackWithOneValue); 

const predecessorStack = resultPair(fst);    // empty stack
const poppedValue = resultPair(snd);         // 1
const sizeOfStack = size(stackWithOneValue); // n1
const headValue = head(stackWithOneValue); // 1
const result = hasPre(stackWithOneValue); // false (as church-boolean)
const stackWithTwoElements = push(push(emptyStack)("Hello"))("World");

getElementByIndex(stackWithTwoElements)(n1); // "Hello"
getElementByIndex(stackWithTwoElements)(n2); // "World"

getElementByIndex(stackWithTwoElements)(1); // "Hello"
getElementByIndex(stackWithTwoElements)(2); // "World"

getElementByIndex(stackWithTwoElements)(999); // Error "invalid index"
const stackWithTwoElements = push(push(emptyStack)(1))(2);
const arrayWithTwoElements = convertStackToArray(stackWithTwoElements); // [1, 2]
const array = [1, 2, 3];
const stack = convertArrayToStack(array); // stack: 1, 2, 3
const stackWithTwoElements = push(push(emptyStack)(1))(2);
const reversedStack = reverseStack(stackWithTwoElements); // stack: 2, 1
const stackWithNumbers  = convertArrayToStack([0,1,2]);

const reduceFunctionSum = acc => curr => acc + curr;
reduce( reduceFunctionSum )( 0 )( stackWithNumbers )          // returns  3
reduce( reduceFunctionSum )( 0 )( push(stackWithNumbers)(3) ) // returns  5
reduce( reduceFunctionSum )( 5 )( stackWithNumbers )          // returns  8
reduce( reduceFunctionSum )( 5 )( push(stackWithNumbers)(3) ) // returns 10

const reduceToArray = acc => curr => [...acc, curr];
reduce( reduceToArray )( [] )( stackWithNumbers )              // returns [0, 1, 2]
const stackWithTwoElements = push(push(emptyStack)(1))(2);
const multiplyWithTwo = x => x * 2;

const mappedStack = map(stackWithTwoElements)(multiplyWith2); // stack: 2, 4
const mapWithReduce = s => map => reduce(s)(pair(acc => curr => push(acc)(map(curr)))(emptyStack));
const stackWithThreeElements = push(push(push(emptyStack)(1))(2))(3);
const filterFunction = x => x > 1 && x < 3;

const filteredStack = filter(stackWithTwoElements)(filterFunction); // stack: 2
const filterWithReduce = s => filter => reduce(s)(pair(acc => curr => filter(curr) ? push(acc)(curr) : acc)(emptyStack));
const stackWithNumbers = startStack(pushToStack)(5)(pushToStack)(10)(id);

const callbackFunc = (element, index) => {
    console.log('element at: ' + index + ': ' + element);
};

forEach(stackWithNumbers)(callbackFunc); // element at: 1: 5
                                         // element at: 2: 10
const stackWithThreeElements = push(push(push(emptyStack)(1))(2))(3);
logStackToConsole(stackWithThreeElements);
const result = startStack(pushToStack)(2)(pushToStack)(3)(pushToStack)(4)(id); // Stack: 2, 3, 4
ListMaparrow-up-right
Stackarrow-up-right
Triplearrow-up-right
Java HashMaparrow-up-right
Empty-ListMaparrow-up-right
getElementByKeyarrow-up-right
removeByKeyarrow-up-right
convertObjToListMaparrow-up-right
convertListMapToArrayarrow-up-right
mapListMaparrow-up-right
filterListMaparrow-up-right
emptyListMaparrow-up-right
reduceListMaparrow-up-right
logListMapToConsolearrow-up-right
throwarrow-up-right
Maybe Type
eitherTruthyarrow-up-right
Liste mit JavaScript 'falsy' Wertenarrow-up-right
eitherNotNullAndUndefinedarrow-up-right
eitherElementOrCustomErrorMessagearrow-up-right
eitherDomElementarrow-up-right
eitherNumberarrow-up-right
eitherNaturalNumberarrow-up-right
eitherFunctionarrow-up-right
eitherTryCatcharrow-up-right
eitherElementsOrErrorsByFunctionarrow-up-right
JavaScript Rest Parameterarrow-up-right
fmap
sondern mit
fold
durchgeführt.

hashtag
Beispiel Anwendung

hashtag
Code ohne Verwendung von Box

hashtag
Code mit Verwendung von Box

hashtag
Verwendung

circle-info

In den folgenden Beispielen wird die Box zur besseren Übersicht wie folgt dargestellt:

{ content }

circle-info

Die Titel der Funktionen sind mit einem Link zur Implementation verknüpft.

hashtag
Boxarrow-up-right

Die Funktion Box wird verwendet um einen beliebigen Wert in eine "Box" zu verpacken.

circle-info

In anderen Programmiersprachen kann diese Methode verglichen werden mit der statischen Methode.of. Die Funktion ist also eine Art Box.of() Methode.

hashtag
fmaparrow-up-right

Die Funktion fmap wird verwendet um den Inhalt einer Box zu verarbeiten (mappen). Diese fmapFunktionsaufrufe können beliebig oft hintereinander angewendet werden (chainning von Funktionen). Durch das "chainning" wird eine Art Pipeline aufgebaut.

hashtag
foldarrow-up-right

Die Funktion fold wird verwendet um einen Wert in der "Box" zu mappen und anschliessend zu extrahieren (den Inhalt aus der Box auszupacken).

circle-info

Diese Funktion wird meistens am Schluss in einer Box Pipeline verwendet, um den Wert nach dem letzten Verarbeitungsschritt zu entpacken.

hashtag
chain (flatMap)arrow-up-right__

Die Funktion chain wird verwendet um ein flatMap durchzuführen. Wenn eine Map-Funktion eine Box erstellt, würde mit fmap eine Box in einer Box entstehen. Um diese extra Box zu entfernen bzw. das gemappte Ergebnis abzuflachen gibt es die Methode chain. Dadurch können auch geschachtelte Box Aufrufe stattfinden.

hashtag
getContentarrow-up-right

Die Funktion getContent wird verwendet um den Inhalt einer "Box" zu entpacken.

hashtag
apparrow-up-right

Die Funktion app wird verwendet um eine eingepackte Funktion (Funktion in einer Box) auf einen eingepackten Wert anzuwenden.

circle-info

Dieses "Design Pattern" oder diese app-Funktion zusammen mit der Box-Funktion bilden eine Applikativearrow-up-right.

hashtag
liftA2arrow-up-right

Die Funktion liftA2 wird verwendet um eine Funktion auf zweit eingepackte Werte anzuwenden.

hashtag
Helferfunktion

hashtag
debugarrow-up-right

Die Funktion debug ist eine Helferfunktion, die für debug Zwecke da ist. Die Funktion hilft dem Anwender die Zwischenresultate zu untersuchen in einer Pipeline.

circle-info

Wichtig bei der debug Funktion ist, das die Funktion fold am Schluss zwingend verwendet werden muss, um das letzte debug Statement auch auszuführen.

hashtag
Box Featurings

hashtag
Stack

hashtag
HttpGet

hashtag
Box mit Maybe

Um die die Box Konstruktion mit Maybe Werten zu verwenden, gibt es spezielle Funktion, die das verarbeiten von Maybe Types erleichtern. Somit wird das prozessieren mit dem Maybe Type vereinfacht und die Maybe Types können verknüpft werden.

circle-info

Wenn irgendwo ein Nothing zurück geliefert wird, wird die Funktionskette abgebrochen und die restlichen Funktionen werden nicht ausgeführt.

hashtag
fmapMaybearrow-up-right

Die Funktion fmapMaybe entspricht der Funktion fmap für einen Maybe Type.

hashtag
foldMaybearrow-up-right

Die Funktion foldMaybe entspricht der Funktion fold für einen Maybe Type

circle-info

foldMaybe entspricht der Funktion mapMaybe``

hashtag
chainMaybearrow-up-right

Die Funktion chainMaybe entspricht der Funktion chain für einen Maybe Type.

circle-info

Die Funktion chainMaybe verwendet die Funktion flatMapMaybe``

hashtag
appMaybearrow-up-right

Die Funktion appMaybe entspricht der Funktion app für einen Maybe Type.

hashtag
liftA2Maybearrow-up-right

Die Funktion liftA2Maybe entspricht der Funktion liftA2 für einen Maybe Type.

circle-info

Falls ein Parameter (fx, fy oder beide) Nothing sind, ist das Gesamtergebnis der Funktion Nothing.

hashtag
Church-Boolean

hashtag
True & False

True kann durch die Funktion Kestrel ausgedrückt werden. False kann durch die Funktion Kite ausgedrückt werden.

Implementation

hashtag

hashtag
Not

Der boolesche not Operator kann mit der Funktion Cardinal ausgedrückt werden.

Implementation & Beispiele:

hashtag
And

Die And-Funktion nimmt zwei Church-Booleans entgegen und liefert ein Church-Boolean zurück. Die Funktion funktioniert genau gleich wie der and-Operator in der mathematischen Logik.

Implementation:

Beispiele:

hashtag

hashtag
Or

Die Or-Funktion nimmt zwei Church-Booleans entgegen und liefert ein Church-Boolean zurück. Die Funktion funktioniert genau gleich wie der or-Operator in der mathematischen Logik.

Implementation:

Beispiele:

hashtag

hashtag
Boolean Equality

Diese Funktion nimmt zwei Church-Booleans entgegen und vergleicht diese miteinander. Nur wenn beide gleich sind, gibt die Funktion ein Church-True zurück, sonst ein Church-False.

Implementation:

Beispiele:

hashtag

hashtag
Show Boolean

Die Funktion showBoolean ist eine Helferfunktion um eine String Repräsentation, eines Church-Boolean zu erhalten. Die Funktion nimmt ein Church-Boolean entgegen und gibt die String Repräsentation davon zurück.

Implementation:

Beispiele:

hashtag

hashtag
Connvert to js Bool

Die Funktion convertToJsBool nimmt ein Church-Boolean entgegen und liefert die JavaScript Representation davon zurück.

Implementation:

Beispiele:

hashtag
Church-Zahlen

Die Church-Zahlen sind keine "echte" Zahlen, sondern eine Funktionen wird n-Mal auf ein Argument angewendet. Um die Zahl Eins als eine Church-Zahl ( n1) zu repräsentieren muss es eine Funktion geben die einmal auf das Argument angewendet wird.

Implementation der Church-Zahl n1 (Eins):

Das gleiche mit den Zahlen von Zwei bis Neun, welche jeweils n-Mal auf ein Argument angewendet werden.

Die Zahl Null n0 wird in den Church-Zahlen als Funktion die keinmal auf das Argument angewendet wird. Somit wird die Funktion f ignoriert.

Implementation der Church-Zahl n0 (Null):

circle-info

n0 nimmt zwei Parameter und gibt den zweiten zurück. Gleich wie die Funktion: Kite (n0 === KI).

hashtag
jsNum

Um eine Church-Zahl in eine JavaScript-Zahl zu transferiere, evaluiert die Funktion jsNum die Church-Zahl n-Mal den Funktionsaufruf und zählt dabei die Aufrufe.

hashtag
churchNum

Um aus einer JavaScript-Zahl eine Church-Zahl zu kreieren, wird mit der Funktion churchNum rekursiv n-Mal mit der Nachfolger-Funktion successor eine Church-Zahl gebaut.

hashtag
Mathematische Operationen mit Church-Zahlen

hashtag
Successor (Nachfolger)

Der Successor nimmt eine Church-Zahl und gibt dessen Nachfolger zurück.

Implementation:

Beispiel:

hashtag

hashtag
Phi (-Kombinator)

Der Phi-Kombinator nimmt eine Pair und gibt ein neues Pair zurück. Der erste Wert entspricht dem zweiten des alten Pairs. Der zweite Wert ist der Nachfolger des zweiten Wertes vom alten Pair.

Implementation:

Beispiel:

hashtag
Predecessor (Vorgänger)

Der Predecessor nimmt eine Church-Zahl und gibt dessen Vorgänger zurück.

circle-info

Der Phi-Kombinator ist dabei eine unterstützende Funktion um den Vorgänger der Church-Zahl zu definieren.

Implementation:

Beispiel:

hashtag
Church-Addition (Addieren)

ChurchAddition nimmt zwei Church-Zahlen und gibt den addierten Wert als Church-Zahl zurück.

circle-info

Der Successor ist dabei unterstützende Funktion. Die erste Church-Zahl ruft dabei n-Mal den successorauf und nimmt die zweite Church-Zahl als Summand.

Implementation:

Beispiel:

hashtag
Church-Substraction (Substrahieren)

ChurchSubstraction nimmt zwei Church-Zahlen und gibt den subtrahierten Wert als Church-Zahl zurück.

circle-info

Der Predecessor ist dabei eine unterstützende Funktion. Die zweite Church-Zahl ruft dabei n-Mal den pred als Subtrahend und nimmt die erste Church-Zahl als Minuend.

Implementation:

Beispiel:

hashtag
Church-Multiplication (Multiplizieren)

ChurchMultiplication nimmt zwei Church-Zahlen und gibt den multiplizierten Wert als Church-Zahl zurück.

circle-info

Die ChurchMultiplication entspricht exakt dem Bluebird !

Implementation:

Beispiel:

hashtag
Church-Potency (Potenzieren)

ChurchPotency nimmt zwei Church-Zahlen und gibt den potenzierende Wert als Church-Zahl zurück.

circle-info

Die ChurchPotency entspricht exakt dem Thrush !

Implementation:

Beispiel:

hashtag
isZero

isZero nimmt eine Church-Zahlen und gibt ein Church-Boolean zurück. Wenn die Church-Zahl n0 ist gibt die Funktion ein Church-Boolean True, ansonsten False zurück.

circle-info

Beachte den Kestrel k in der Funktion, der nur zum Zug kommt, wenn die Church-Zahl nicht n0 ist und somit den ersten Wert bzw. False zurück gibt.

Implementation:

Beispiel:

hashtag
leq (less-than-or-equal)

leq nimmt zwei Church-Zahlen und gibt ein Church-Boolean zurück. Wenn der erste Wert kleiner oder gleich dem zweiten Wert ist gibt die Funktion ein Church-Boolean True, ansonsten False zurück.

circle-info

isZero und churchSubstraction sind dabei die benötigten Funktionen um Leq zu implementieren. churchSubstraction substrahiert die erste Church-Zahl mit der zweiten Church-Zahl. Der substrahierte Wert ist n0 , wenn die zweite Church-Zahl grösser oder gleich der ersten Church-Zahl ist. Wenn dies stimmt, gibt isZero ein True zurück.

Implementation:

Beispiel:

hashtag
eq (equality-to)

eq nimmt zwei Church-Zahlen und gibt ein Church-Boolean zurück. Wenn die beiden Church-Zahlen gleich sind, gibt die Funktion das Church-Boolean True, ansonsten False zurück.

circle-info

And und Leq sind dabei die unterstützende Funktionen. Mit a_nd_ und leq werden die Church-Zahlen auf ihre Äquivalenz geprüft. Wenn dies Stimmt, erhält a_nd_ zwei True-Werte von leq zurück.

Implementation:

Beispiel:

hashtag
gt (greater-than)

gt nimmt zwei Church-Zahlen und gibt ein Church-Boolean zurück. Wenn der erste Wert grösser als der zweite Wert ist, gibt die Funktion ein Church-Boolean True, ansonsten False zurück.

circle-info

Blackbird, Not und Leq sind dabei die unterstützende Funktionen. Der Blackbird handelt die not und leq-Funktion (not(leq(n)(k) ). Dabei wird nichts andere als der Output bzw. die Church-Boolean der leq-Funktion von der not-Funktion negiert.

Implementation:

Beispiel:

hashtag

Lambda-Kombinatoren
Alonzo Churcharrow-up-right
const listMap = stack; // triple
const emptyListMap = listMap(n0)(id)( pair(id)(id) );
const listMapWithOneValue = push(emptyListMap)( pair(1)("Hello") ) // [(1, "Hello")]

const listMapWithTwoValue = push(listMapWithOneValue)( pair(42)("World") ) // [(1, "Hello"), (42, "World")]
const p1 = pair(1)("Michael")
const p2 = pair(2)("Peter")
const p3 = pair(3)("Hans")

const testListMap = convertArrayToStack([p1, p2, p3]) // [ ("1", "Michael"), ("2", "Peter"),("3", "Hans") ]

getElementByKey( testListMap )( 1 )   // "Michael"
getElementByKey( testListMap )( 2 )   // "Peter"
getElementByKey( testListMap )( 3 )   // "Hans"
const p1 = pair(1)("Michael")
const p2 = pair(2)("Peter")
const p3 = pair(3)("Hans")

const testListMap   = convertArrayToStack( [p1, p2, p3] )
const resultListMap = removeByKey(testListMap)(2); // [ ("1", "Michael"), ("3", "Hans") ]

getElementByKey( resultListMap )( 1 )   // "Michael"
getElementByKey( resultListMap )( 3 )   // "Hans"
// Implementation
const convertObjToListMap = obj => 
    Object.entries(obj).reduce((acc, [key, value]) => push(acc)(pair(key)(value)), emptyListMap);

// Anwendung
const personObject = {firstName: "George", lastName: "Lucas"}

const result = convertObjToListMap(personObject); // [ ("firstName", "George"), ("lastName","Lucas") ]

getElementByKey( result )( "firstName" )   // "George"
getElementByKey( result )( "lastName"  )   // "Lucas"
// Person-Constructor
const personCtor = fstName => lstName => age => convertObjToListMap({fstName, lstName, age});
// create Person-Tuples
const chuck = personCtor("Chuck")("Norris")(42);
const peter = personCtor("Peter")("Pan")(102);

// accessor functions
getElementByKey( chuck )( "fstName" )  ===  "Chuck"  
getElementByKey( chuck )( "lstName" )  ===  "Norris" 
getElementByKey( chuck )( "age"     )  ===  42     
  
getElementByKey( peter )( "fstName" )  ===  "Peter"  
getElementByKey( peter )( "lstName" )  ===  "Pan"    
getElementByKey( peter )( "age"     )  ===  102      
// Implementation
const convertListMapToArray = listMap => 
    reduceListMap(acc => curr => [...acc, curr])([])(listMap);

// Anwendung
const personObject  = {firstName: "George", lastName: "Lucas"}

const personListMap = convertListMapToArray( personObject ); // [ ("firstName", "George"), ("lastName","Lucas") ]

convertListMapToArray( personListMap ) // [ "George", "Lucas" ]
// Implementation
const mapListMap = f => map(p => pair( p(fst) )( f(p(snd)) ));

// Anwendung
const toUpperCase      = str => str.toUpperCase();
const listMapWithNames = convertObjToListMap({name1: "Peter", name2: "Hans"});

const mappedListMap    = mapListMap(toUpperCase)(listMapWithNames); // [ ("name1", "PETER"), ("name2", "HANS") ]

getElementByKey( mappedListMap )( "name1" ) // "PETER"
getElementByKey( mappedListMap )( "name2" )  // "HANS"
// Implementation
const filterListMap    = f => filter(p => f(p(snd)));

// Anwendung
const startsWithP      = str => str.startsWith('P');

const listMapWithNames = convertObjToListMap( {name1: "Peter", name2: "Hans", name3: "Paul"} );
const filteredListMap  = filterListMap( startsWithP )( listMapWithNames ); // [ ("name1", "Peter"), ("name3", "Paul") ]

getElementByKey( filteredListMap )( "name1" );  // "Peter"
getElementByKey( filteredListMap )( "name3" );  // "Paul"
// Implementation
const reduceListMap = f => reduce(acc => curr => f(acc => curr(snd)));

// Anwendung
const reduceFunc = acc => curr => acc + curr.income;

const listMapWithPersons = convertObjToListMap({
              p1: {firstName: 'Peter', income: 1000},
              p2: {firstName: 'Michael', income: 500}
        });
    
reduceListMap(reduceFunc)(0)(listMapWithPersons); // 1500
// Implementation
const logListMapToConsole = listMap =>
    forEach(listMap)( (element, index) => console.log("At Index " + index + " is  Key and Element " + JSON.stringify(element(fst)) + " | " + JSON.stringify(element(snd)) ));
    
// Anwendung
const listMapWithPersons = convertObjToListMap( {firstName: "George", lastName: "Lucas"} );

logListMapToConsole( listMapWithPersons );

// Logs to Console:
// Index 1 (Key, Element): ("firstName", "George")
// Index 2 (Key, Element): ("lastName", "Lucas")
const Left   = x => f => _ => f (x);
const Right  = x => _ => g => g (x);
// Anwendung        
eitherXYZ(someParam)
    (error  => doSomethingInErrorCase(error)    )  // Left Case
    (result => doSomethingInSuccessCase(result) )  // Right Case
const eitherTruthy = value =>
    value
        ? Right(value)
        : Left(`'${value}' is a falsy value`);
const eitherNotNullAndUndefined = value =>
    value !== null && value !== undefined
        ? Right(value)
        : Left(`element is '${value}'`);
// Implementation
const eitherElementOrCustomErrorMessage = errorMessage => element =>
    eitherNotNullAndUndefined(element)
     (_ => Left(errorMessage))
     (_ => Right(element));
 
// Anwendung       
eitherElementOrCustomErrorMessage("Der Wert ist Null")(null); // Left ("Der Wert ist null")
const eitherDomElement = elemId =>
    eitherElementOrCustomErrorMessage
     (`no element exist with id: ${elemId}`)
     (document.getElementById(elemId));
const eitherNumber = val =>
    Number.isInteger(val)
        ? Right(val)
        : Left(`'${val}' is not a integer`);
const eitherNaturalNumber = val =>
    Number.isInteger(val) && val >= 0
        ? Right(val)
        : Left(`'${val}' is not a natural number`);
const eitherFunction = val =>
    typeof val === "function"
        ? Right(val)
        : Left(`'${val}' is not a function`);
const eitherTryCatch = f => {
    try {
        return Right(f());
    } catch (error) {
        return Left(error);
    }
}
eitherElementsOrErrorsByFunction:: (a -> Either a) -> [a] -> Either [a]
eitherElementsOrErrorsByFunction(eitherDomElement)("inputText", "output")
(err    => doSomethingWithErrorMessages) // stack mit Fehlermeldungen
(result => {                             // listMap mit den Resultaten

   // Die Resultate als einzelne Variablen
   const [inputText, output] = convertListMapToArray(result);
   
   doSomethingWithResult(inputText, output);
   
})
const p = {firstName: "Lukas", lastName: "Mueller"};

const addSalutation = fullName => male => (male ? "Mr. " : "Mrs. ") + fullName;

const fullName           = p.firstName + " " + p.lastName;
const fullNameUpperCase  = fullName.toUpperCase();
const nameWithSalutation = addSalutation(fullNameUpperCase)(true); // Mr. LUKAS MUELLER
const p = {firstName: "Lukas", lastName: "Mueller"};

const addSalutation = fullName => male => (male ? "Mr. " : "Mrs. ") + fullName;

const nameWithSalutation = Box(p)
                             (fmap)(p => p.firstName + " " + p.lastName)
                             (fmap)(fullName => fullName.toUpperCase())
                             (fold)(fullNameUpperCase => addSalutation(fullNameUpperCase)(true)); // Mr. LUKAS MUELLER
// Implementation
const fmap  = x => f => g => g(f(x));  

const Box   = x => fmap(x)(id);        // Box.of

// Anwendung
Box(10);                 // { 10 }
Box("Hello World");      // { "Hello World" }
Box(p);                  // { p }
// Implementation
const fmap = x => f => g => g(f(x));

// Anwendung
Box(5)                                 // { 5 }
 (fmap)(n => n * 10)                   // { 50 }
 (fmap)(n => n + 15)                   // { 65 }
 (fmap)(n => String.fromCharCode(n));  // { 'A' }
// Implementation
const fold  = x => f => f(x);

// Anwendung
Box(5)                                 // { 5 }
 (fmap)(n => n * 10)                   // { 50 }
 (fmap)(n => n + 15)                   // { 65 }
 (fold)(n => String.fromCharCode(n));  // 'A'
// Implementation
const chain = x => f => g => g((f(x)(id)));

// Anwendung
Box(5)                                     // { 5 }
 (fmap)(num => num + 5)                    // { 10 }
 (chain)(num => Box(num * 2)
                 (fmap)(num => num + 1))   // { 21 }
 (chain)(num => Box(num * 3)
                 (fmap)(num => num + 1))   // { 64 }
// Implementation
const getContent = b => b(id);

// Anwendung
const p = { firstName: "Tyrion", lastName: "Lannister" };

const box1 = Box(p);

const mapped1 = box1(fmap)(p => p.firstName);

const mapped2 = mapped1(fmap)(firstName => firstName.toUpperCase());

getContent( box1    )   // { firstName: "Tyrion", lastName: "Lannister" } 
getContent( mapped1 )   // "Tyrion"
getContent( mapped2 )   // "TYRION"
// Implementation
const app = x => f => g => g(f(fmap)(x)(id));

// Anwendung
Box(x => x + 5)          // { 10 + 5 }
 (app)( Box(10) );       // { 10 }
 
Box( x => y => x + y)    // { 10 + 24 }
 (app)( Box(10) )        // { 10 }
 (app)( Box(14) );       // { 24 }
// Implementation
const liftA2 = f => fx => fy => fx(fmap)(f)(app)(fy);

// Anwendung
liftA2(name1 => name2 => name1 + " " + name2)  // { "Tyrion Lannister" }
 ( Box("Tyrion"   ) )
 ( Box("Lannister") );
// Implementation
const debug = x => {
    console.log(x);
    return x;
}

// Anwendung
Box(10)
 (fmap)(debug)        // Ausgabe auf der Konsole: 10
 (fmap)(n => n + 2)
 (fold)(debug);       // Ausgabe auf der Konsole: 12
const Stream = (...elements) => Box(convertArrayToStack(elements));

Stream(1,2,3,4)
 (fmap)( map(x => x * 2)    )
 (fold)( filter(x => x > 4) )
// Synchron
Box( HttpGetSync( URL ) )
 (fmap)( JSON.parse   )
 (fold)( x => x.value )
 
 
// Asynchron
HttpGet( URL )(resp => Box(resp)
                        (fmap)(JSON.parse   )
                        (fold)(x => x.value ))
// Implementation
const fmapMaybe = x => f => g => g(mapMaybe(x)(f));

// Anwendung
 const maybePerson = () => Just( {firstName: "Tyrion", lastName: "Lannister"} );
 
 const maybeFirstName = obj =>
      obj && obj.hasOwnProperty('firstName')
          ? Just(obj.firstName)
          : Nothing;
          
 Box( maybePerson() )                                  // { Just({firstName: "Tyrion", lastName: "Lannister"}) }
  (chainMaybe)( maybeFirstName )                       // { Just("Tyrion") }
  (foldMaybe)( firstName => firstName.toUpperCase() )  //   Just("TYRION")
// Implementation
const foldMaybe = mapMaybe;

// Anwendung
Box( Just(10) )                   // { Just(10) }
 (fmapMaybe)(x => x + 10)         // { Just(20) }
 (foldMaybe)(num => num + '$')    // Just("20$")
// Implementation
const chainMaybe    = x => f => g => g(flatMapMaybe(x)(f));

// Anwendung
const maybePerson = () => Just({firstName: "Tyrion", lastName: "Lannister"});

const maybeFirstName = obj =>
        obj && obj.hasOwnProperty('firstName')
            ? Just(obj.firstName)
            : Nothing;

Box( maybePerson() )                                  // { Just({firstName: "Tyrion", lastName: "Lannister"}) 
 (chainMaybe)( maybeFirstName )                       // { Just("Tyrion") }
 (foldMaybe)( firstName => firstName.toUpperCase() )  //   Just("TYRION")
// Implementation
const apMaybe = x => f => g => g(flatMapMaybe(x)(func => mapMaybe(f)(func)));

// Anwendung
Box( Just(x => x + 5) )          // { Just(15 + 5) }
 (appMaybe)( Just(10) );         // { Just(10) }
// Implementation
const liftA2Maybe = f => fx => fy =>
    Box(fx)
     (fmapMaybe)(f)
     (appMaybe)(fy);
        
// Anwendung
liftA2Maybe( x => y => x + y )  // (10 + 5)
 ( Just(10) )
 ( Just(5)  );
const True  = K;
const False = KI;
const not = C;

not(True);         // False (Function)
not(False);        // True  (Function)
not(not(True));    // True  (Function)
const and = p => q => p(q)(False);
and(True)(True)         // True
and(False)(True)        // False
and(True)(False)        // False
and(False)(False)       // False
const or = p => q => p(True)(q);
or(True)(True)         // True
or(False)(True)        // True
or(True)(False)        // True
or(False)(False)       // False
const beq = p => q => p(q)(not(q));
beq(True)(True)         // True
beq(False)(True)        // False
beq(True)(False)        // False
beq(False)(False)       // True
const showBoolean = b => b("True")("False");
showBoolean(True);        // 'True'
showBoolean(False);       // 'False'
const convertToJsBool = b => b(true)(false);
convertToJsBool(True)        // true
convertToJsBool(False)       // false
// Implementation n1
const n1 = f => a => f(a);

// Demonstration
n1(x => x + 1)(0)      // 1

n1(x => x + '!')('λ')  // 'λ!'
// Implementation n2...n9
const n2 = f => a => f(f(a));
const n3 = f => a => f(f(f(a)));
const n4 = f => a => f(f(f(f(a))));
const n5 = f => a => f(f(f(f(f(a)))));
const n6 = f => a => f(f(f(f(f(f(a))))));
const n7 = f => a => f(f(f(f(f(f(f(a)))))));
const n8 = f => a => f(f(f(f(f(f(f(f(a))))))));
const n9 = f => a => f(f(f(f(f(f(f(f(f(a)))))))));

// Demonstration
n2(x => x + 1)(0)      // 2
n3(x => x + 1)(0)      // 3
n4(x => x + 1)(0)      // 4

n3(x => x + '!')('λ')  // 'λ!!!'
// Implementation n0
const n0 = f => a => a;

// Demonstration
n0(x => x + 1)(0)      // 0

n0(x => x + '!')('λ')  // 'λ'
// Implementaion
const jsNum = n => n(x => x + 1)(0);

// Anwendung
jsNum(n0)     // 0
jsNum(n1)     // 1
jsNum(n2)     // 2
// Implementaion
const churchNum = n => n === 0 ? n0 : successor(churchNum(n - 1));

// Anwendung
jsNum(0)     // n0
jsNum(1)     // n1
jsNum(2)     // n2
const successor = n => f => a => f(n(f)(a));
successor(n0)        // n1
successor(n5)        // n6
const phi = p => pair(p(snd))(succ(p(snd)));
const testPair  = pair(n1)(n2);
const testPhi   = phi(testPair);

testPhiPair(fst)    // n2
testPhiPair(snd)    // n3
const pred = n => n(phi)(pair(n0)(n0))(fst);
 pred(n0)   // n0
 pred(n1)   // n0
 pred(n2)   // n1
 pred(n9)   // n8
const churchAddition = n => k => n(successor)(k);
churchAddition(n0)(n0)     //  0
churchAddition(n1)(n0)     //  1
churchAddition(n2)(n5)     //  7
churchAddition(n9)(n9)     // 18
const churchSubtraction = n => k => k(pred)(n);
churchSubtraction(n2)(n1)     // 1
churchSubtraction(n2)(n0)     // 2
churchSubtraction(n2)(n5)     // 0
churchSubtraction(n9)(n4)     // 5
const churchMultiplication = B;    // Bluebird
churchMultiplication(n2)(n1)     //  1
churchMultiplication(n2)(n0)     //  0
churchMultiplication(n2)(n5)     // 10
churchMultiplication(n9)(n4)     // 36
const churchPotency = T;    // Thrush
churchPotency(n2)(n1)     //    2
churchPotency(n2)(n0)     //    1
churchPotency(n2)(n5)     //   32
churchPotency(n9)(n4)     // 6561
const is0 = n => n(K(False))(True);
is0(n0)     // True
is0(n1)     // False
is0(n2)     // False
is0(n7)     // False
const leq = n => k => is0(churchSubtraction(n)(k));
leq(n0)(n0)     // True
leq(n0)(n3)     // True
leq(n5)(n5)     // True
leq(n5)(n1)     // False
const eq = n => k => and(leq(n)(k))(leq(k)(n));
 eq(n0)(n0)  // True
 eq(n0)(n1)  // False
 eq(n1)(n1)  // True
 eq(n2)(n1)  // False
const gt = Blackbird(not)(leq);
gt(n0)(n0)     // False
gt(n0)(n1)     // False
gt(n1)(n1)     // False
gt(n2)(n1)     // True 
hashtag
Listener erstellen

Als erstes wird ein Listener erstellt. Ein Listener ist ein Schlüssel-Wert Paar, dessen Wert eine Funktion ist, die bei einer Wertänderung auf dem Observable, aufgerufen wird. Somit kann mit dieser Funktion auf eine Wertänderung reagiert werden. Diese Funktion nimmt zwei Parameter entgegen, als erstes dennewValue und als zweites den oldValue.

In diesem Beispiel wird die Variable listenerVariable immer mit dem newValue-Wert überschrieben, wenn der Listener vom Observable über eine Wertänderung benachrichtigt wird. oldValue wird in diesem Beispiel nicht verwendet.

hashtag
Observable erstellen und Listener registrieren

Nachdem ein 'Zuhörer' (Listener) erstellt wurde, braucht es noch den 'Erzähler' (Observable). Dafür gibt es die Funktion Observable welche als ersten Parameter den initialen Wert entgegennimmt. Mit der Funktion addListener wird der zuvor erstellte Listener registriert.

circle-info

Nachdem einer Listener mit einem Observable verknüpft ist, erhält der Listener sofort den aktuellsten Stand (initialen Wert) vom Observable. In diesem Beispiel die Zahl '42'.

hashtag
Aktueller Wert abfragen

Die Funktion getValue gibt den aktuellen Wert aus dem Observable zurück.

hashtag
Wertänderung

Mit der Funktion setValue wird dem Observable ein neuer Wert mitgeteilt. Alle verbundene Listener werden benachrichtig und der neue Wert als newValue __mitgegeben. Der vorherige Wert als oldValue. Die Funktion setValue gibt ein neues Observable zurück.

hashtag
Listener entfernen

Wenn ein Listener wieder von einem Observable entfernt werden soll, gibt es dafür die FunktionremoveListener. Diese Funktion gibt wieder ein Observable zurück.

Der zuvor entfernte Listener bekommt nun keine Wertänderungen mehr mit.

hashtag
Zusammenfassung:

hashtag
Observable Text-Input Beispiel

In diesem Beispiel-Projekt gibt es ein 'Observable', welches auf die Wertänderungen eines Text-Input-Feldes auf dem UI reagiert. Dabei werden alle 'Listener' mit dem neuen und alten Wert informiert.

circle-info

In der Demo sind die Checkboxen neben den Labels zum entfernen und hinzufügen der Listener da.

Screenshot Text-Input Example

hashtag
Demo

circle-info

Es gibt vorgefertigte Listener-Funktionen, welche im Beispiel benutzt werden.

hashtag
Implementation

Für den vollen Code: observableTextInputExample.jsarrow-up-right

hashtag
Observable Color-Picker Beispiel

In diesem Beispiel-Projekt wird gezeigt wie ein Color-Picker mit dem Observable gebaut werden kann. Es gibt ein Observable das die Farbe verwaltet, an welches sich Listener wie Background, Labels und Inputs registrieren können. Die Input-Felder (Text-Input und Slider) sind dabei nicht nur Listener sondern auch gleichzeitig dafür da, dem Observable neue Werte zu übermitteln. Die Elemente Text-Input und Slider-Input sind bidirektional mit dem Observerable verbunden. Um das zu demonstrieren wurden Buttons im UI hinzugefügt zum an- und abmelden der Listener.

Screenshot Color-Picker Example

hashtag
Demo

hashtag
Implementation

circle-info

Der observierte Farbwert ist als Triple implementiert:triple(red, green, blue)

Für den vollen Code: observableColorPickerExample.jsarrow-up-right****

hashtag
Observable HttpGet-Joke Beispiel

In diesem Beispiel-Projekt gibt es ein Observable das Witze verwaltet. Die Witze werden mit Klick auf den Button von einem REST-API abgefragt. Sobald ein neuer Witz veröffentlich wird, werden alle Listener informiert. Es existieren zwei Listener, der eine rendert die Witze auf dem UI und der andere löst ein Text-To-Speech-Skript aus.

Screenshot Joke-Example

hashtag
Demo

hashtag
Implementation

Für den vollen Code: observableHttpGetJokeExample.jsarrow-up-right****

hashtag
HTTP-Programmierschnittstelle

Für dieses Beispiel wurde extra eine Funktionen erstellt um HTTP-Get anfragen zu tätigen. Sie bieten einen einfachen Weg, Daten von einer URL zu erhalten.

hashtag
****(asynchron)

Mit der Funktion HttpGet wird asynchrone anfrage abgesetzt. Die Anfrage wird nach 30 Sekunden Time-out automatisch beendet, wenn vom Webserver bis dahin keine Antwort kommt. Die Funktion HttpGet erwartet als ersten Parameter eine URL und als zweiten Parameter eine Callback-Funktion __mit der Antwort vom Webserver.

Beispiel:

hashtag
****HttpGetSync arrow-up-right****

Analog zu HttpGet gibt es die Synchrone-Variante: HttpGetSync. Denn Callback braucht es nicht, da der Response direkt als Rückgabewert zurück gegeben werden kann.

Beispiel mit Box:

hashtag
Verwendung

circle-info

Die Titel der Funktionen sind mit einem Link zur Implementation verknüpft.

hashtag
Observablearrow-up-right

Die Funktion Observable nimmt einen initialen Startwert und erstellt ein Observable.

hashtag
Observable-Funktionen

hashtag
observableBodyarrow-up-right (der Kern des Observable)

Das Observable-Konstrukt observableBody repräsentiert der Körper der Observable-Funktionen:

  • ****addListener

  • removeListener

  • removeListenerByKey

Der observableBody wird bei diesen Funktion immer zurückgegeben. Es ermöglicht eine Verkettung der Funktionen mit einem Observable.

triangle-exclamation

Nachdem anwenden einer Observable-Funktion ist es wichtig den Rückgabewert in einer Variablen zu speichern, weil dieser das aktuelle Observable enthält. Anschliessend kann darauf immer weitere Observable-Funktion angewandt werden.

circle-info

Die Variable, die das Observable enthält, kann mit dem const Schlüsselwort deklariert werden und ist somit auch immutable. Dadurch kann diese Variable nicht überschrieben werden und es können dann keine Listener hinzugefügt werden oder entfernt werden.

hashtag
****addListenerarrow-up-right****

Mit der Funktion addListener wird dem Observable ein neuer Listener hinzugefügt.

circle-info

Der aktuelle Wert des Observables wird beim Registrieren sofort dem neuen Listener mitgeteilt.

triangle-exclamation

Das Observable sollte nicht mit mehr als 5'000 Listener verbunden werden, weil ansonsten ein "Uncaught RangeError: Maximum call stack size exceeded" __auftretten könnte.

circle-info

Mit bis zu 100 Listener und vielen Wertänderungen (zb. 100'000) auf einmal hat das Observable kein Problem.

hashtag
removeListenerarrow-up-right

Die Funktion removeListener entfernt den übergebenen Listener aus dem Observable.

hashtag
removeListenerByKeyarrow-up-right

Die Funktion removeListenerByKey entfernt ein Listener aus dem Observable anhand des übergeben Schlüssels.

hashtag
****setValuearrow-up-right****

Mit der Funktion setValue wird dem Observable ein neuer Wert gegeben. Das Observable informiert danach alle Listener.

hashtag
****getValuearrow-up-right****

Mit der Funktion getValue erhält man den aktuellen Wert vom Observable.

hashtag
newListenerWithCustomKeyarrow-up-right

Mit der Funktion newListenerWithCustomKey wir ein neuer Listener erstellt. Die Funktion nimmt als erstes den Schlüssel, als zweites die Funktion, die auf die Wertänderung reagiert, entgegen.

triangle-exclamation

Der Schlüssel muss mit dem JavaScript "===" - Operator verglichen werden können.

triangle-exclamation

Der Schlüssel von einem Listener muss eindeutig sein in einem Observable.

hashtag
****newListenerarrow-up-right****

Mit der Funktion newListener wir ein neuer Listener erstell. Der Key muss im Vergleich zu newListenerWithCustomKey nicht angeben werden, weil dieser automatisch generiert wird.

circle-info

Der generateRandomKey erzeugt einen String der Länge sechs mit zufälligen Buchstaben (Gross-/Kleinschreibung) & Zahlen. Siehe implementation: generateRandomKeyarrow-up-right

hashtag
setListenerKeyarrow-up-right

Mit der Funktion setListenerKey wird einem Listener ein neuer Schlüssel zugewiesen.

hashtag
getListenerKeyarrow-up-right

Mit der Funktion getListenerKey wird der Schlüssel von einem Listener abgefragt.

hashtag
Helferfunktion

hashtag
logListenersToConsolearrow-up-right

Mit der Funktion logListenersToConsole werden die Listener eines Observables auf der JavaScript Konsole ausgegeben.

Lambda-Kalkül hat im Grunde nichts in sich. Es hat nur drei Dinge: Variablenbindung, einen Weg, Funktionen zu bauen und einen Weg, Funktionen anzuwenden. Es hat keine anderen Kontrollstrukturen, keine anderen Datentypen, gar nichts.

hashtag
Was ist JavaScript?

JavaScript ist die Programmiersprache die hauptsächlich im Web verwendet wird und durch den Browser ausgeführt wird. JavaScript integriert dabei viele funktionale Aspekte, stellt aber auch einiges an Funktionalität aus der objektorientierten Programmierung zur Verfügung. Es besteht also die Möglichkeit, in vielen verschiedenen Paradigmen zu programmieren.

hashtag
Lambda Kalkül & JavaScript

JavaScript hat den Ruf, eine unsichere Programmiersprache zu sein. Man kann aber auch in JavaScript sichere und belastbare Konstruktionen mit Industriehärte bauen. Ein Weg dazu ist die Anwendung von Erkenntnissen aus den Grundlagen der Informatik, dem untypisierten Lambda-Kalkül. Das Konzept ist, Lambda Kalkül mit der Programmiersprache JavaScript zu verbinden. Das heisst, in nur rein funktionalen Paradigmaarrow-up-right Program-Codes zu schreiben (purely functional). JavaScript bietet dazu Sprachelemente wie Closuresarrow-up-right und Funktionenarrow-up-right. Sie machen es möglich, dass in JavaScript funktional programmiert werden kann. Es gewährleistet die Konzepte der Seiteneffektfreiheit, Zustandslosigkeit, Variablenbindung statt Zuweisung, Funktionskomposition und Funktionen höherer Ordnung (high order functions) zu schreiben.

hashtag
Forschungsarbeit

Ziel dieser Forschungsarbeit ist es, neue Konstruktionen aus dem untypisierten Lambda Kalkül, mit der Programmiersprache JavaScript zu entwerfen. Diese Konstruktionen haben das Ziel JavaScript Applikationen robuster, sicherer und wartbarer zu machen. Bei diesen Konstruktionen wird komplett auf die Werte der reinen funktionalen Programmierungarrow-up-right gesetzt:

  • Reinheit (pure functions): Funktionen ohne Seiteneffekte (wie mathematische Funktionen)

  • Unveränderlichkeit (immutable Datastructure): __ Unveränderliche Datenstrukturen

  • Iteration: Eine Iteration ohne Ausdrücke wie for, while oder do Schleifen

  • Fehlerbehandlung ohne throw Ausdruck - Errorhandling mit oder

  • Funktionen höherer Ordnung (high order functions).

  • Zustandslosigkeit

Abgrenzung von objektorientierter Programmierung: Es werden keine objektorientierte Konzepte wie Klassen oder Vererbung verwendet.

hashtag
Was du hier findest

Eine Sammlung von Konstruktionen heraus:

kleine Bibliothek von Lambda-Kalkül-Konstruktionen zusammengestellt (Einfache Kombinatoren)

  • kleine Bibliothek von Lambda-Kalkül-Konstruktionen (Einfache Kombinatoren)

  • Rechnen mit JavaScript- und mit Church-Zahlen inklusivem lambdafizierter Taschenrechner

  • Eigene unveränderliche Datenstruktur (die immutable Stack Datenstruktur)

  • Datenstruktur (Stack mit Schlüssel-Wert Paaren)

  • Umsetzung des Observer Pattern ()

  • und Type für Fehlerbehandlung

  • -Konstrukt um Werte in einer Pipeline zu verarbeiten

  • Eignes mit einer Zeitmessung für die Methodenausführung ()

  • (Dokumentation und Typ-Unterstützung für Anwender)

  • Code Convention (Konzepte, Formatierung und Anwendungsbeispiele)

hashtag
Classic JS vs Lambda JS

hashtag
Property Value aus Objekt extrahieren (null-safe)

Gegeben: Ein verschachteltes User-Objekt mit Street-Property. Ziel: Strassenname extrahieren

hashtag
Eine mögliche Implementierung im klassischen JavaScript (ohne Optional Chaining) wäre:

hashtag
Eine gleichwertige Implementierung mit Verwendung des Werkzeugkastens (Lambda JS):

hashtag
Vergleich

Eigenschaften

Classic JS

Lambda JS

Variablen für Zwischenstände

wird benötigt

keine

Verschachtelung von If Statements

wird benötigt

hashtag
Pure Lambda JS vs Lambda JS

Bereits eine kleine Funktion wie push , die ein Stack mit einem neuen Wert erstellt , besteht im Kern aus mehreren Funktionen.

hashtag
Die Implementation der Funktion push sieht wie folgt aus:

Sie besteht aus folgenden Funktionen: stack, succ, stackIndex .Diese Funktionen können in der Funktion push durch ihre Implementation ersetzt werden:

const stack = triple

const triple = x => y => z => f => f(x)(y)(z);

const succ = n => f => x => (f)(n(f)(x));

const stackIndex = firstOfTriple;

const firstOfTriple = x => y => z => x;

hashtag
Die Funktion push würde im reinen Lambda Kalkül (pure Lambda JS) wie folgt aussehen:

hashtag
Nach der Eta-Reduktion:

Funktionen in JS im reinen Lambda Kalkül zu schreiben kann schnell unübersichtlich werden weil die Definitionen fehlen. Diese verschachtelten anonymen Funktion werden schnell zu komplex. Darum ist es besser dieses Funktionskonstrukt in mehreren Funktionen aufzuteilen und diesen einen sinnvollen Namen zu geben.

hashtag
Beispiel an der grösseren Funktion reduce

hashtag
Implementation von reduce in Lambda JS:

hashtag
Implementation in pure Lambda JS (Funktionsdefinitionen ersetzt und ETA reduziert)

Die Performance leidet wenn eine grössere, komplexere Funktion in einer reinen Lambda Kalkül Schreibweise definiert ist. Da es keine Definitionen gibt die wiederverwendet werden können muss viel mehr evaluiert werden in JavaScript. Darum ist es für die Performance und für die Leserlichkeit besser die Funktionen nicht in der reinen Lambda Kalkül Schreibweise zu definieren.

hashtag
Fazit / Erkenntnisse

hashtag
Konzepte aus der funktionalen Programmierung

Die Konstruktionen beinhalten Ideen und Konzepte aus der funktionalen Programmierung. Mit dem Einsatz dieser Konstruktionen, können JavaScript Applikationen funktionaler implementiert werden. Die Konstruktionen sind so implementiert, dass sie leicht integrierbar und anwendbar sind. Ein JavaScript Programm muss dabei nicht komplett nur aus diesen Konstruktionen bestehen, sondern der Anwender kann hier und dort bestimme Konstrukte in sein Programm einfliessen lassen.

hashtag
Nutzen & Vorteile

In mehreren kleinen Beispielen hat sich gezeigt, dass die Konstruktionen den Code leserlicher, wartbarer und sicherer machen. Ausserdem entstehen weniger typische Fehler, die bei der Programmierung mit JavaScript auftreten.

Da die Konstruktionen aus puren Funktionen bestehen, ist der Programmablauf klarer und Fehler können besser eingegrenzt werden. Bei veränderlichen Daten und Funktionen mit Seiteneffekten, leidet die Übersicht, man verliert die Kontrolle über den Programmablauf und den Abhängigkeiten innerhalb des Programmes. Schon durch einen kleinen Einsatz von diesen Konstruktionen wird diesem Problem entgegenwirkt und die Übersicht wird verbessert.

hashtag
JS Doc Unterstützung - Fehlendes Typ System bei JavaScript

Ein wesentliches Ziel von Typisierung in Programmiersprachen ist die Vermeidung von Laufzeitfehlern. JavaScript ist eine schwach typisierte oder dynamische Programmiersprache. Datentypen werden bei einer Variable nicht explizit deklariert und jede Variable kann mit Werten jedes Typen initialisiert werden. Es gibt auch kein Compiler der die Typen überprüfen würde. Die JS Doc unterstützt den Anwender für die korrekte Verwendung der Funktionen. Mit der JS Doc bekommt der Anwender Hinweise auf die korrekten Typ-Parameter.

‌‌Potenzielle Erweiterungen/Vorschläge für nächste Schritte

  • Für die unveränderlichen Datenstrukturen Stack und ListMap könnten zusätzliche Funktionen entwickelt werden, sodass ein noch grösseres Anwendungsgebiet entsteht.

  • Mögliche Funktionen: findFirst, stream-artige Funktionen

  • Weitere Konzepte der funktionalen Programmierung umsetzen

Was kann verbessert werden?

  • Bei gewissen Funktionen könnte noch mehr Sicherheit eingebaut werden, sodass ungültige Parameter besser abgefangen werden

  • Noch mehr Funktionen die auch ein Maybe/Either Type zurückgeben

  • Mehr Funktionen mit aussagekräftigen Fehlermeldungen für den Verwender

Diese Arbeit erstanden aus einem Projekt (IP5) und der Bacherlorarbeit (IP6) an der FHNWarrow-up-right:

  • Benjamin Brodwolf GitHubarrow-up-right

  • Pascal Andermatt GitHubarrow-up-right

Herzlichen Dank an unseren Projektbetreuer und Inspirator Prof. Dierk Königarrow-up-right

Alonzo Churcharrow-up-right
Calculator.htmlarrow-up-right
Thrush-Funktion
Identitäts-Funktion
Church-Zahlen
lambdafizierte Arithmetik-Operatoren mit Church-Zahlen
lambdafizierte Arithmetik-Operatoren
Church-Zahlen
jsNum
Calculator.htmlarrow-up-right

Immutable Stack Erweiterungen

Neue Funktionen für den Stack: concat, flatten, zipWith, zip, stackEquals, getElementByIndex, removeByIndex, getIndexOfElement, maybeIndexOfElement, containsElement, convertElementsToStack

hashtag
Basis

Die Funktionen in diesem Kapitel sind neu zum Immutable Stack hinzugekommen.

Immutable Stackchevron-right
triangle-exclamation

Der Index bei einem Stack beginnt bei 1. Der Index 0 ist reserviert für den . Am Index 0 steht immer das Element id.

circle-info

In den folgenden Beispielen wird zur besseren Übersicht, die Stack Datenstruktur wie folgt dargestellt:[ element1, element2, element3, ... ]

hashtag
Erweiterungen

circle-info

Die Titel der Funktionen sind mit einem Link zur Implementation verknüpft

hashtag

Die Funktion concat nimmt zwei Stacks entgegen und konkateniert diese.

hashtag

Die Funktion flatten nimmt einen Stack entgegen, dessen Einträge Stacks sind. Die Funktion verknüpft diese alle zusammen zu einem Stack. Das Tiefenlevel, bis zu welcher die Struktur abgeflacht wird ist 1.

hashtag

Die zipWithFunktion nimmt eine Verknüpfungsfunktion und zwei Stacks entgegen. Anhand der Verknüpfungsfunktion werden die Elemente der beiden übergebenen Stacks paarweise miteinander verknüpft zu einem neuen Stack.

circle-info

Wenn einer der beiden übergebenen Stacks kürzer ist wird nur bis zum letzten Element des kürzeren Stacks verknüpft.

hashtag

Die zip Funktion nimmt zwei Stacks entgegen und verknüpft die beiden Stacks mit der Funktion pair.

circle-info

Wenn einer der beiden übergebenen Stacks kürzer ist wird nur bis zum letzten Element des kürzeren Stacks verknüpft.

hashtag

Die Funktion stackEquals nimmt zwei Stacks entgegen und vergleicht alle Elemente mit dem JavaScript === Operator auf Gleichheit. Wenn alle Vergleiche true ergeben, gibt die Funktion ein Church-Boolean True ansonsten ein Church-Boolean False zurück.

hashtag

Die Funktion getElementByIndex nimmt einen Stack und eine oder JS-Zahl, die den Index des Elements repräsentiert, entgegen. Falls an diesem Index ein Element existiert, wird dieses zurückgegeben ansonsten wird auf der Console einer Error geloggt und der Rückgabewert ist undefined.

hashtag
Anwendungs Beispiel:

circle-info

Der Anwender muss nicht mehr entscheiden, welche Funktionen er braucht: getElementByChurchNumberIndex oder getElementByJsNumIndex. Die Funktion getElementByIndexwurde erweitert, dass der Index auf den "Typ" kontrolliert wird mittels eitherFunction und eitherNaturalNumber. So kann der Anwender eine Church- oder JavaScript-Zahl angeben, die Funktion findet selber heraus, welche Methode er braucht. Bei ungültigen Parametern werden die passende Fehler-Meldungen geloggt.

circle-info

Die spezifischeren Funktionen um ein Element zu erhalten sind weiterhin vorhanden:

  • ````

hashtag

Die Funktion removeByIndex nimmt einen Stack und eine oder JS-Zahl als Index entgegen. Die Funktion löscht das Element am übergebenen Index und gibt den neuen Stack zurück. Bei einem nicht existierenden Index erhält man denselben Stack unverändert zurück.

hashtag

Die Funktion getIndexOfElement nimmt einen Stack und ein Element entgegen und gibt den Index als JavaScript-Zahl von diesem Element zurück. Wenn das Element nicht existiert wird undefined zurückgegeben.

hashtag

Die Funktion maybeIndexOfElement ist analog zur Funktion . Nur der Rückgabetyp ist ein .

hashtag

Die Funktion containsElement nimmt einen Stack und ein Element entgegen. Gibt True (ChurchBoolean) zurück, wenn das Element im Stack vorhanden ist. Gibt False (ChurchBoolean) zurück, wenn das Element nicht im Stack vorhanden ist.

hashtag

Die Funktion convertElementsToStack nimmt einen Rest Parameter () entgegen. Die übergebenen Elemente werden in ein Stack umgewandelt.

Test-Framework

hashtag
Beschreibung

Um die Korrektheit unserer Konstruktionen zu verifizieren haben, wir ein eigenes Test Framework ohne externe Abhängikeiten geschrieben. Von der Architektur (Aufbau/Struktur & Konzept) haben wir uns vom Test Framework von Prof. Dierk König inspieren lassen. Ausserdem wurde der Immutable-Stack im Test-Framework eingebaut und verwendet. Zudem sind mehrere nützliche Funktionen vom Stack verwendet worden, wie z.b die Filter- & forEach-Funktion.

hashtag
Ansicht aller Testergebnissen:

hashtag
Aufbau

Zuerst ein paar wichtige Definition/Erklärungen zum Test-Framework.

Die kleinste Einheit unseres Test-Frameworks ist ein einzelner Aufruf der equals-Methode auf dem Assert Objekt.

Die equals-Methode nimmt zwei Parameter entgegen. Das erste Argument ist der tatsächliche Wert (z.b der Wert vom Aufruf, einer zu testenden Funktion). Der zweite Parameter ist der erwartete Wert. Nun vergleicht die equals-Funktion die beiden übergebenen Parameter auf Gleicheit. Wenn der erwartete Wert mit dem IST-Zustand übereinstimmt, so ist der Test erfolgreich.

Pro Konstruktion/Funktion gibt es mehrere solche Test's mit der equals-Methode. Diese werden dann zu einem Testfall für die Funktion zusammengefasst.

Beispiel eines Testfalles:

Bei diesem Testfall wird die getestet. Der Testfall besteht hier aus 5 Tests.

Mehrer zusammengehörende Testfälle werden einer Testsuite hinzugefügt. Dies dient dazu um mehrere Testfälle zu gruppieren. Zum Beispiel werden alle Testfälle vom Stack einer Testsuite hinzugefügt.

In der Testsuite befinden Testfälle von allen Funktionen des Gruppierten Thema's(Im Bild oben alle Testfälle von allen Stack-Konstruktionen). Dabei sieht man bei einem einzelnen Testfall einer Funktion, wie viele Test's erfolgreich waren.

Wenn in einem Testfall ein Test fehlschlägt, wird dies farblich angezeigt. Dabei wird die Nummer des Test's und was genau schiefgegangen ist angezeigt.

Die Testsuiten werden am Schluss auf einer Html Seite angezeigt, sodass man eine Übersicht von allen Test's hat. Auf dieser Übersicht werden alle Test's von allen Testfällen zusammengezählt und als Total angezeigt.

Benchmark und unsere Erkenntnisse

hashtag
BenchmarkTest

Um Funktionen auf ihre Ausführungsgeschwindigkeit zu prüfen wurde die Funktion BenchmarkTest dem TestFramework hinzugefügt. Mit der Zeitstempel Methode performance.now() von JavaScript **** kann die Ausführungsdauer der Funktion methodUnderTest berechnet werden. Die Werte, die von Performance.now() zurückgegeben werden sind immer in einem konstanten Tempo, unabhängig von der Systemuhr.

hashtag
Unsere Erkenntnisse mit der Funktion BenchmarkTest

hashtag
forEach-Methode des

Bei der Anwendung des Observables ist uns aufgefallen, dass die Benachrichtigung der Listener viel zu lange ging. Wir haben uns danach gefragt, welcher Teil so viel Zeit in Anspruch nimmt für die Ausführung. Danach untersuchten wir die forEach Methode, die bei der Benachrichtigung der Listener eine zentrale Rolle spielt. Wir haben die Methode analysiert und festgestellt dass in jeder Iterationsrunde eine weitere Iteration gestartet wird (ähnlich wie eine for-Schleife in einer for-Schleife), die jedoch nicht benötigt wird.

Anstelle den Index in jeder Iterationsrunde aus dem Stack via jsnum zu berechnen, wurde der Index in jeder Iteration um eins erhöht und der nächsten Iterationsrunde mitgegeben. Das Problem bei der Funktion jsnum ist das diese mit einer rekursiven Implementation eine Church Zahl in eine JavaScript Zahl umwandelt.

Nach dem Refactoring war die forEach Methode massiv viel schneller (mehr als doppelt so schnell) . Wir haben einen kleinen Benchmark Test erstellt, der misst wie lange eine Ausführung dauert und konnten dadurch einen erheblichen Unterschied feststellen.

Implementation: forEachvor dem Refactoring

Implementation: forEach nach dem Refactoring

Einfache Kombinatoren

hashtag
Beschreibung

Folgende Konstruktionen dienen als Grundbausteine für unsere späteren Implementationen. Diese Grundbausteine kommen zum Teil aus dem Lambda Kalkül.

hashtag

Code Convention

hashtag
Naming

Im allgemeinen wird auf sprechende Namen gesetzt, sodass die Funktionen selbsterklärend sind.

Bei Abfragefunktionen, mit welcher der Anwender einen Wert anfordert, gibt der jeweilige Präfix des Funktionsnamens Aufschluss, von welchem Typ der Rückgabewert sein wird.

listenerVariable   // 42
obsExample = obsExample(setValue)(66);

listenerVariable         // 11 <- no updates anymore
obsExample( getValue );  // 66
/*
    Listener-Functions
 */
const listenerLogToConsole                        =            nVal => oVal => console.log(`Value: new = ${nVal}, old = ${oVal}`)
const listenerNewValueToDomElementTextContent     = element => nVal => oVal => element.textContent = nVal
const listenerOldValueToDomElementTextContent     = element => nVal => oVal => element.textContent = oVal
const listenerNewValueLengthToElementTextContent  = element => nVal => oVal => element.textContent = nVal.length                
Box( HttpGet(jokeUrl) )
 (mapf)( JSON.parse )
 (fold)( x => getDomElement("joke").textContent = x.value) )
let obsExample = Observable(0)

obsExample = obsExample( addListener    )( /* dein Listener   */ )
obsExample = obsExample( removeListener )( /* dein Listener   */ )
obsExample = obsExample( setValue       )( /* dein neuer Wert */ )
const listenerLog = newListener( listenerLogToConsole  );

// 'const' deklariert die Observable als eine Konstante (immutable)
const obsExample = Observable(0)
                     (addListener)( listenerLog )

// Die Observable kann nicht mehr verändert werden
obsExample = obsExample( removeListener)( listenerLog ) // entfernen nicht möglich
obsExample = obsExample( addListener   )( listenerLog ) // hinzufügen nicht möglich
let listenerVariable; // undefined
const listenerExample = newListener( newValue => oldValue  => listenerVariable = newValue );
let obsExample = Observable(42)                     // new Observable with initValue 42
                  (addListener)( listenerExample ); // append Listener to the Observable
obsExample( getValue );  // 42
obsExample = obsExample( setValue )(11) // set new value and update all listeners

listenerVariable         // 11
obsExample( getValue );  // 11
obsExample = obsExample( removeListener )( listenerExample ); 
let listenerVariable; // undefined
const listenerExample = newListener( nVal => oVal => listenerVariable = nVal );

// create observable and add listener
let obsExample = Observable(42)
                    (addListener)(listenerExample); 

listenerVariable // 42 <- get the value from initialValue

// set new value and update listeners
obsExample = obsExample(setValue)(11);

// receive the update
listenerVariable // 11 

// remove listener
obsExample = obsExample(removeListener)(lisExample);  

// set new value and update listeners
obsExample = obsExample(setValue)(67); 

// receive no updates anymore 
listenerVariable // 11  
// Get the elements from the Dom
const [inputText, newValue, oldValue, sizes] = getDomElements("inputText", "newValue", "oldValue", "sizes");

// Create Listener
const listenerNewValue      = newListener( listenerNewValueToDomElementTextContent     (newValue) );
const listenerOldValue      = newListener( listenerOldValueToDomElementTextContent     (oldValue) );
const listenerNewValueSize  = newListener( listenerNewValueLengthToElementTextContent  (sizes)    );
const listenerConsoleLog    = newListener( listenerLogToConsole                                   );

// Create Observable-Object, define the Initial-Value and append the Listeners
let textInputObservables = Observable("")
                            (addListener)( listenerNewValue     )
                            (addListener)( listenerOldValue     )
                            (addListener)( listenerNewValueSize )
                            (addListener)( listenerConsoleLog   );

// Connect the Observables with the Input-Text-Field.
// Every change in the Input-Field execute the 'setValue'-Function with the new value from Input-Field.
inputText.oninput = _ =>
    textInputObservables = textInputObservables(setValue)(inputText.value);
// Get the elements from the Dom
const [resultColor, rgbValue, hex, hsl] = getDomElements("resultColor", "rgbValue", "hex", "hsl");
const [inputR, inputG, inputB]          = getDomElements("inputR", "inputG", "inputB");
const [rangeR, rangeG, rangeB]          = getDomElements("rangeR", "rangeG", "rangeB");

// Getter methods for the RPG-Values (triple)
const getRed    = firstOfTriple;
const getGreen  = secondOfTriple;
const getBlue   = thirdOfTriple;

// Create Listeners for every color (red, green, blue) to Text- & Slider-Input
const listenerInputR       = newListener( nVal => _ => inputR.value  =  nVal( getRed   ));
const listenerRangeR       = newListener( nVal => _ => rangeR.value  =  nVal( getRed   ));
const listenerInputG       = newListener( nVal => _ => inputG.value  =  nVal( getGreen ));
const listenerRangeG       = newListener( nVal => _ => rangeG.value  =  nVal( getGreen ));
const listenerInputB       = newListener( nVal => _ => inputB.value  =  nVal( getBlue  ));
const listenerRangeB       = newListener( nVal => _ => rangeB.value  =  nVal( getBlue  ));

// Create Listeners for the Background-Result, RGB- & Hex-Labels
const listenerBgColorRGB   = newListener( nVal => _ => resultColor.style.backgroundColor = toRGBString( nVal(getRed), nVal(getGreen), nVal(getBlue) ));
const listenerRgbTextRGB   = newListener( nVal => _ => rgbValue.value                    = toRGBString( nVal(getRed), nVal(getGreen), nVal(getBlue) ));
const listenerHexTextRGB   = newListener( nVal => _ => hex.textContent                   = toHexString( nVal(getRed), nVal(getGreen), nVal(getBlue) ));

// Create Observable-Object, define the three initial-Values RGB and append the Listeners
let rgbObservable = Observable(triple(55)(215)(150))
                                (addListener)( listenerInputR     )
                                (addListener)( listenerRangeR     )
                                (addListener)( listenerInputG     )
                                (addListener)( listenerRangeG     )
                                (addListener)( listenerInputB     )
                                (addListener)( listenerRangeB     )
                                (addListener)( listenerBgColorRGB )
                                (addListener)( listenerRgbTextRGB )
                                (addListener)( listenerHexTextRGB );
                                
// Connecting the Observables with every Input-Field (Range and Text).
inputR.oninput = _ =>
    rgbObservable = rgbObservable(setValue)(
        triple
            (inputR.value)
            (rgbObservable(getValue)(getGreen))
            (rgbObservable(getValue)(getBlue))
    );

rangeR.oninput = _ =>
    rgbObservable = rgbObservable(setValue)(
        triple
            (rangeR.value)
            (rgbObservable(getValue)(getGreen))
            (rgbObservable(getValue)(getBlue))
    );

...
// Either all the necessary Dom-Element exist and or display all missed Element
eitherElementsOrErrorsByFunction(eitherDomElement)("jokeHistory", "norrisBtn", "nerdyBtn", "trumpBtn")
(err => document.body.innerHTML = Box(err)(mapf)(convertStackToArray)(mapf)(s => s.join(", <br>"))(fold)(txt => `<div style="background: orangered"> <br> ${txt}</div>`))
(result => {

    // receive founded the elements
    const [jokeHistory, norrisBtn, nerdyBtn, trumpBtn] = convertListMapToArray(result)

    // create the Listeners (text-to-speech & display to view)
    const listenerSpeak     = newListener(nValue => oValue => speak(nValue(snd)));

    const listenerJokeToDom = newListener(nValue => oValue => {
        const template = document.createElement('fieldset');
        template.className = "joke"
        template.innerHTML = `<legend>${nValue(fst)}</legend><p class="jokeText">${nValue(snd)}</p>`
        jokeHistory.insertAdjacentElement('afterbegin', template)
    });

    // create the Observable with pair data structure ("Title")("Joke")
    const jokePairObserver = Observable( pair("nobody")("tell me a joke") )
                                    (addListener)( listenerSpeak )
                                    (addListener)( listenerJokeToDom )


    // Jokes-API-URLs
    const jokeNorrisUrl = "https://api.chucknorris.io/jokes/random";            // jsonKey: value
    const jokeNerdUrl   = "https://v2.jokeapi.dev/joke/Programming?type=single" // jsonKey: joke
    const trumpTweetUrl = "https://www.tronalddump.io/random/quote";            // jsonKey: value


    // Constructor for a Joke-Object
    const jokeCtor = name => btn => url => jsonKey => convertObjToListMap({name, btn, url, jsonKey});

    // creat Joke-Object
    const norrisJoke = jokeCtor("Chuck Norris - Joke")(norrisBtn)(jokeNorrisUrl)("value");
    const nerdJoke   = jokeCtor("Nerd - Joke"        )(nerdyBtn )(jokeNerdUrl  )("joke" );
    const trumpTweet = jokeCtor("Trump Tweet"        )(trumpBtn )(trumpTweetUrl)("value");

    // combine the Joke-Objects into a stack
    const jokes = convertElementsToStack(norrisJoke, nerdJoke, trumpTweet);

    // add the Joke-Buttons a on-click event listener for request the Jokes and update the Observable
    forEach(jokes)((joke, _) =>
        getElementByKey(joke)("btn").onclick = _ =>
            HttpGet( getElementByKey(joke)("url") )(resp =>
                jokePairObserver(setValue)(Box(resp)
                                            (mapf)(JSON.parse)
                                            (fold)(x => pair( getElementByKey(joke )( "name"))( x[getElementByKey(joke)("jsonKey")] )))));
})
// Implementation
const Observable = initialValue =>
    observableBody(emptyListMap)(initialValue)(setValue)(initialValue);
    
// Anwendung
const obsExample = Observable(0)
// Implementation
const observableBody = listeners => value => observableFn =>
    observableFn(listeners)(value);
// Implementation
const addListener = listeners => value => newListener => {
    newListener(snd)(value)(value)
    return observableBody( push(listeners)(newListener) )(value)
    
// Anwedung
const obsExample = Observable(0)
                    (addListener)( listenerLogToConsole      )
                    (addListener)( listenerNewValueToElement );
// Implementation
const removeListener = listeners => value => givenListener =>
    observableBody( removeByKey(listeners)(givenListener(fst)) )(value)

  
// Anwendung
const listenerLog = newListener(listenerLogToConsole);

let obsExample = Observable(0)
                     (addListener)( listenerLog );
                     
obsExample = obsExample(removeListener)( listenerLog );    
// Implementation
const removeListenerByKey = listeners => value => listenerKey =>
    observableBody(removeByKey(listeners)(listenerKey))(value)

  
// Anwendung
const listenerLog = newListenerWithCustomKey(42)(listenerLogToConsole);

let obsExample = Observable(0)
                     (addListener)( listenerLog );
                     
obsExample = obsExample(removeListenerByKey)(42)   
// Implementation
const setValue = listeners => oldValue => newValue => {
    forEach(listeners)((listener, _) => (listener(snd))(newValue)(oldValue))
    return observableBody(listeners)(newValue)
}


// Anwendung
let obsExample = Observable(0)
testObs(getValue)                // 0
testObs = testObs(setValue)(42)
testObs(getValue)                // 42
// Implementation
const getValue = listeners => value => value;

  
// Anwendung
let obsExample = Observable(0)
testObs(getValue)                // 0
testObs = testObs(setValue)(42)
testObs(getValue)                // 42
// Implementation
const newListener = listenerFn => pair(generateRandomKey())(listenerFn);

  
// Anwendung
const listenerLog = newListenerWithCustomKey(42)(listenerLogToConsole);
// Implementation
const newListener = listenerFn => pair(generateRandomKey())(listenerFn);

  
// Anwendung
const listenerLog = newListener(listenerLogToConsole);
// Implementation
const setListenerKey = newKey => listener => pair(newKey)(listener(snd));

  
// Anwendung
let listenerLog = newListener(listenerLogToConsole);
listenerLog = setListenerKey( listenerLog  )(42)
// Implementation
const getListenerKey = listener => listener(fst);

  
// Anwendung
const listenerLog = newListenerWithCustomKey(42)(listenerLogToConsole);
getListenerKey( listenerLog )  // 42
// User-Object
const user = {
    firstName: "Donald",
    lastName: "Duck",
    address: {
        city: "Entenhausen",
        street: {
            name: "WaltStreet",
            nr: 10
        }
    }
}

// Anwendungs Ziel
streetName(user) // "WALTSTREET"
const streetName = user => {
    if (user){
        const address = user.address;
        if(address){
            const street = address.street;
            if(street){
                const name = street.name;
                if (name){
                    return name.toUpperCase();
                }
            }
        }
    }
    return "no street"
}
const streetName = user =>
    Box(maybeNotNullAndUndefined(user))
        (chainMaybe)(u => maybeNotNullAndUndefined(u.address))
        (chainMaybe)(a => maybeNotNullAndUndefined(a.street))
        (chainMaybe)(s => maybeNotNullAndUndefined(s.name))
        (foldMaybe)(n => n.toUpperCase())
    (_ => "no street")
    (id)
const push = s => stack( succ( s(stackIndex) ) )(s);
const push = s => (x => y => z => f => f(x)(y)(z))((n => f => x => (f)(n(f)(x)))(s(x => _ => _ => x)))(s)
const push = s => z => f => f( f => x => f( s(x => _ => _ => x)(f)(x) ))(s)(z);
// reduce in mehreren Funktionen unterteilt
const reduce = reduceFn => initialValue => s => {

    const reduceIteration = argsTriple => {
        const stack = argsTriple(firstOfTriple);

        const getTriple = argsTriple => {
            const reduceFunction    = argsTriple(secondOfTriple);
            const preAcc            = argsTriple(thirdOfTriple);
            const curr              = head(stack);
            const acc               = reduceFunction(preAcc)(curr);
            const preStack          = stack(stackPredecessor);
            return triple(preStack)(reduceFunction)(acc);
        }

        return If( hasPre(stack) )
                (Then( getTriple(argsTriple) ))
                (Else(           argsTriple  ));
    };

    const times = size(s);
    const reversedStack = times
                            (reduceIteration)
                                (triple
                                  (s)
                                  (acc => curr => push(acc)(curr))
                                  (emptyStack)
                                )
                                (thirdOfTriple);

    return times
            (reduceIteration)
                (triple
                  (reversedStack)
                  (reduceFn)
                  (initialValue)
            )(thirdOfTriple);
};
// reduce in reinem Lambda Kalkül 
const reduce = reduceFn => initialValue => s => s(x => _ => _ => x)(t => t(x => _ => _ => x)(x => _ => _ => x)(_ => (_ => y => y))(x => _ => x)(t)((t => f => f(t(x => _ => _ => x)(_ => y => _ => y))(t(_ => y => _ => y))(t(_ => y => _ => y)(t(_ => _ => z => z))((t(x => _ => _ => x))(_ => _ => z => z))))(t)))(f => f(s(x => _ => _ => x)(t => t(x => _ => _ => x)(x => _ => _ => x)(_ => (_ => y => y))(x => _ => x)(t)((t => f => f(t(x => _ => _ => x)(_ => y => _ => y))(t(_ => y => _ => y))(t(_ => y => _ => y)(t(_ => _ => z => z))((t(x => _ => _ => x))(_ => _ => z => z))))(t)))(f => f(s)(acc => curr =>  f => f( f => x => f(s(x => _ => _ => x)(f)(x)))(acc)(curr))(f => f(_ => a => a)(x => x)(x => x)))(_ => _ => z => z))(reduceFn)(initialValue))(_ => _ => z => z);
const calculatorHandler = op => n => k => f => f(op(n)(k));
const plus              = n => k => n + k;
const multiplication    = n => k => n * k;
const subtraction       = n => k => n - k;
const exponentiation    = n => k => n ** k;
const division          = n => k => n / k;

// example
plus(1)(2)            // 3
multiplication(2)(3)  // 6 
subtraction(5)(2)     // 3
const add   = calculatorHandler(plus);            
const multi = calculatorHandler(multiplication);  
const sub   = calculatorHandler(subtraction);
const pow   = calculatorHandler(exponentiation);
const div   = calculatorHandler(division);
T(1)(add)(2)(pow)(6)(sub)(2)(div)(8)(add)(7)(multi)(4)(sub)...
T(1)(add)(2)(id)                     //   3
T(1)(sub)(2)(id)                     //  -1
T(5)(multi)(4)(add)(2)(id)           //  22
T(5)(div)(5)(multi)(100)(add)(1)(id) // 101
const calc   = T;
const result = id;
calc(5)(multi)(4)(sub)(4)(pow)(2)(div)(8)(add)(10)(result) // 42
const churchAdd     = calculatorHandler(churchAddition);
const churchMulti   = calculatorHandler(churchMultiplication);
const churchPow     = calculatorHandler(churchPotency);
const churchSub     = calculatorHandler(churchSubtraction);
calc(n5)(churchMulti)(n9)(churchAdd)(n4)(churchSub)(n7)(result) // n42
f => a => f(f(f(f(f(f(f(a))))))); //n7
jsNum(n42) // 42
calc(1)(sub)(7)(result)         // -6

calc(n1)(churchSub)(n7)(result) // 0 bzw. n0
calc(n5)(cpow)(n8)(cmulti)(n6)(cadd)(n8)(csub)(n9) ... // Maximum call stack size exceeded

keine

Leserlichkeit/Lesefluss

erschwert

klarer

Wartbarkeit

schlecht

gut

Either
Maybe
Immutable ListMap
funktionales Oberservable Konstrukt
Maybe
Either
Box
Test-Frameworks
Benchmark-Test
JsDoc
-> siehe Implementation arrow-up-right****
``getElementByJsnumIndexarrow-up-right``

``

Als Either-Variante:

  • ``eitherElementByJsNumIndexarrow-up-right``

  • ``eitherElementByChurchIndexarrow-up-right``

emptyStack
concatarrow-up-right
flattenarrow-up-right
zipWitharrow-up-right
zip (with pair)arrow-up-right
stackEqualsarrow-up-right
getElementByIndexarrow-up-right
Church-
getElementByChurchNumberIndexarrow-up-right
removeByIndexarrow-up-right
Church-
getIndexOfElementarrow-up-right
maybeIndexOfElementarrow-up-right
getIndexOfElement
Maybe
containsElementarrow-up-right
convertElementsToStackarrow-up-right
JavaScript Rest Parameterarrow-up-right
Immutable-Stack
Wichtige Funktionen (Grundbausteine)

hashtag
id - Die Identitätsfunktion

Die Identitätsfunktion nimmt einen Wert entgegen und gibt diesen wieder zurück.

Implementation:

Beispiele:

hashtag

hashtag
Kestrel - Die Konstante Funktion

Die Konstante Funktion nimmt zwei Paramter entgegen und gibt den ersten wieder zurück.

Implementation:

Beispiele:

hashtag

hashtag
Kite

Der Kite ist eine Funktion, die zwei Parameter entgegennimmt und den zweiten Parameter zurückgibt.

Implementation:

Beispiele:

hashtag

hashtag
Mockingbird

Der Mockingbird nimmt einen Funktion entgegen und wendet die Funktion auf sich selber an. (English: self-application)

Implementation:

Beispiele:

hashtag

hashtag
Cardinal (Flip) - Vertauschungsfunktion

Die Vertauschungsfunktion nimmt eine Funktion und zwei Argumente entgegen und wendet die Argumente in vertauschter Reihenfolge auf die übergebene Funktion an.

Implementation:

Beispiel:

hashtag

hashtag
Bluebird - Funktionskomposition

Der Bluebird nimmt zwei Funktionen und ein Argument entgegen. Zuerst wendet der Bluebird das Argument auf die zweite Funktion an und das Resultat wird auf die erste Funktion angewendet. Der Bluebird funktioniert gleich wie die Funktionskomposition in der Mathematik .

Implementation:

Beispiele:

hashtag

hashtag
Thrush

Der Thrush nimmt ein Argument und eine Funktion entgegen. Dieses Argument wendet der Thrush auf die übergebene Funktion an.

Implementation:

Beispiele:

hashtag

hashtag
Vireo

Der Vireo ist eine Funktion, die zwei Argumente und eine Funktion entgegen nimmt. Die Funktion wendet die zwei übergebenen Argumente auf die übergebene Funktion an. Der Vireo ist gleichzeitig eine unveränderliche Datenstruktur, siehe Pair.

Implementation:

hashtag

hashtag
Pair

Das Pair ist eine unveränderliche Datenstruktur bestehend aus zwei Elementen. Mit sogenannten "getter"-Funktionen kann auf diese Werte zugegriffen werden. Für beide Werte des Pairs gibt es eine "getter"-Funktion. Für den ersten Wert des Pairs gibt es die Funktion fst (first), für den zweiten Wert gibt es die Funktion snd (second). Für das Pair und die dazugehörigen getter muss nichts neues implementiert werden, sondern es können dafür bereits bestehende Funktionen (Grundbausteine) verwendet werden. Das Pair ist gerade der Vireo. Die fst-Funktion ist gerade die Konstante Funktion. Die snd-Funktion ist gerade der Kite.

Implementation :

Beispiele:

hashtag

hashtag
MapPair

Die Funktion mapPair nimmt eine map-Funktion und ein Pair entgegen. Die Funktion gibt ein neues Pair mit den gemappten Werten zurück.

Implementation:

Beispiele:

hashtag

hashtag
ShowPair

Die Funktion nimmt ein Pair entgegen und gibt die String Repräsentation des Pairs zurück.

Implementation:

Beispiele:

hashtag

hashtag
Triple

Das Triple ist eine unveränderliche Datenstruktur bestehend aus drei Elementen. Mit sogenannten "getter"-Funktionen kann auf diese Werte zugegriffen werden. Für alle Werte des Triple gibt es eine "getter"-Funktion. Ein Triple ist fast wie ein Pair, nur hat es einen Wert mehr.

Implementation:

Beispiele:

hashtag

hashtag
Blackbird

Der Blackbird ist eine Funktion, die zwei Funktionen und zwei Argumente entgegennimmt. Die zweite Funktion wird auf die zwei übergebenen Argumente angewendet, das Ergebnis wird auf auf die erste Funktion angewendet. Der Blackbird hat ähnlichkeiten mit dem Bluebird.

Implementation:

Beispiele:

hashtag

[**`eitherElementByIndex`**](https://github.com/mattwolf-corporation/ip6_lambda-calculus-in-js/blob/1854cf6515e5f1ba74c48c4a9a97f12e5e363aa2/src/stack/stack.js#L270) **``**
const stack1  = convertArrayToStack( ["Hello", "Haskell"] );
const stack2  = convertArrayToStack( ["World", "Random"] );

concat(stack1)(stack2); // [ "Hello", "Haskell", "World", "Random" ]


const stack3 = convertArrayToStack( [1, 2, 3] );
const stack4 = convertArrayToStack( [4] );
concat(stack3)(stack4)  // [ 1, 2, 3, 4 ]
const s1 = convertArrayToStack( [1, 2] );
const s2 = convertArrayToStack( [3, 4] );
const s3 = convertArrayToStack( [5, 6] );

const stackWithStacks = convertArrayToStack( [s1, s2, s3] ); // [ [1, 2], [3, 4], [5, 6] ]

flatten(stackWithStacks) // [ 1, 2, 3, 4, 5, 6]
const add = x => y => x + y;

const s1 = convertArrayToStack( [1, 2] );
const s2 = convertArrayToStack( [4, 5] );

zipWith(add)(s1)(s2) // [ 5, 7 ]
const s1 = convertArrayToStack( [1, 2] );
const s2 = convertArrayToStack( [3, 4] );

zip(s1)(s2)  // [ (1, 3), (2, 4) ]
const s1 = convertArrayToStack( [1, 2] );
const s2 = convertArrayToStack( [1, 2] );

stackEquals(s1)(s2)  // True (Church Boolean)
const stackWithStrings = convertArrayToStack(["Hello", "World"]);

getElementByIndex(stackWithStrings)(n1) // "Hello"
getElementByIndex(stackWithStrings)(n2) // "World"

getElementByIndex(stackWithStrings)( 1)  // "Hello"
getElementByIndex(stackWithStrings)( 2)  // "World"

getElementByIndex(stackWithStrings)(999) // Error "invalid index" 
const stackWithStrings = convertArrayToStack( ["Hello", "Haskell", "World"] );

removeByIndex(stackWithStrings)( 2) // [ "Hello", "World" ]
removeByIndex(stackWithStrings)(n2) // [ "Hello", "World" ]

removeByIndex(stackWithStrings)(999) // [ "Hello", "Haskell", "World" ]
const stackWithNumbers = convertArrayToStack( [7, 34, 10] );

getIndexOfElement(stackWithNumbers)(7)    // 1
getIndexOfElement(stackWithNumbers)(34)   // 2
getIndexOfElement(stackWithNumbers)(10)   // 3
getIndexOfElement(stackWithNumbers)(100)  // undefined
const stackWithNumbers = convertArrayToStack( [7, 34, 10] );

maybeIndexOfElement(stackWithNumbers)(7)    // Just(1)
maybeIndexOfElement(stackWithNumbers)(34)   // Just(2)
maybeIndexOfElement(stackWithNumbers)(10)   // Just(3)
maybeIndexOfElement(stackWithNumbers)(100)  // Nothing
const stackWithNumbers = convertArrayToStack( [0, 11, 22, 33] );

containsElement(stackWithNumbers)(-1) === False
containsElement(stackWithNumbers)( 0) === True
containsElement(stackWithNumbers)(11) === True
containsElement(stackWithNumbers)(22) === True
containsElement(stackWithNumbers)(33) === True
containsElement(stackWithNumbers)(44) === False
const stackWithValues = convertElementsToStack(1,2,3);

convertStackToArray( stackWithValues ) === [1,2,3]


const stackWithValues2 = convertElementsToStack(1,2,3,...['a','b','c']);

convertStackToArray( stackWithValues2 ) === [1,2,3,'a','b','c']
const BenchmarkTest = mutName => methodUnderTest => {
    const t0 = performance.now(); // Timer start

    const result = methodUnderTest();

    const t1 = performance.now(); // Timer stop

    const milliseconds = t1 - t0; 
    const timeCondition = milliseconds >= 600;
    const time = timeCondition ? milliseconds / 1000 : milliseconds;

    console.log(`Call Method ${mutName} took ${time.toFixed(2)} ${timeCondition ? 'seconds' : 'milliseconds'}.`);

    return result;
}
const forEach = stack => callbackFunc => {
    const times         = size(stack);
    const reversedStack = reverseStack(stack);

    const iteration = s => {
        if( convertToJsBool(hasPre(s)) ) {
            const element = head(s);
            const index   = jsnum( succ( churchSubtraction(times)(size(s) )));

            callbackFunc(element, index);

            return ( pop(s) )(fst);
        }
        return s;
    };

    times
        (iteration)
        (reversedStack);
};
const forEach = stack => callbackFunc => {
    const times         = size(stack);
    const reversedStack = reverseStack(stack);

    const invokeCallback = p => {
        const s       = p(fst);
        const index   = p(snd);
        const element = head(s);

        callbackFunc(element, index);

        return pair( getPreStack(s) )( index + 1 );
    }

    const iteration = p =>
        If( hasPre( p(fst) ))
            ( Then( invokeCallback(p) ))
            ( Else(                p  ));

    times
        (iteration)
        (pair( reversedStack)(1) );
};
const id = x => x;
id(10);           // 10
id(id);           // id
id("Hello");      // "Hello"
const K = x => y => x;
K(1)(2);         // 1
K(8)(id);        // 8
K('q')('t');     // 'q'
const KI = x => y => y;
KI(1)(2);                // 2
KI(id)(3);               // 3
KI("Hello")("World");    // "World"
const M = f => f(f);
M(id);        // id
M(id)(5);     // 5
M(M);         // stack overflow, da M(M) ==> M(M) ==> M(M) ....
const C = f => x => y => f(y)(x);
const diff = x => y => x - y;

C(diff)(2)(3);        //  1
C(diff)(3)(2);        // -1
const B = f => g => x => f(g(x));
    const f = x => x + 1;
    const g = x => x * 2;
    
    B(f)(g)(4);      // 9
    B(g)(f)(4);      // 10
    B(id)(id)(5);    // 5
const T = x => f => f(x);
const f = x => x + 1;

T(2)(f);            // 3
T(2)(id);           // 2 
const V = x => y => f => f(x)(y);
const pair    =    V;    // immutable datastructure pair

const fst     =    K;    // get first element from pair
const snd     =   KI;    // get second element from pair
const pairOfNumbers = pair(1)(2);
const pairOfStrings = pair("Hello")("World");
    
pairOfNumbers(fst);        // 1
pairOfNumbers(snd);        // 2
    
pairOfStrings(fst);        // "Hello"
pairOfStrings(snd);        // "World"
const mapPair = f => p => pair(f(p(fst)))(f(p(snd)));
const mapFunction = x => x * 2;
const pairOfNNumbers = pair(5)(6);

const mappedPair = mapPair(mapFunction)(pairOfNNumbers); // pair(10)(12)
const showPair = p => `${p(fst)} | ${p(snd)}`;
const pairOfNNumbers = pair(5)(6);

const stringOfPair = showPair(pairOfNNumbers); // '5 | 6'
const triple = x => y => z => f => f(x)(y)(z);
// getter functions of triple
const firstOfTriple  = x => y => z => x;
const secondOfTriple = x => y => z => y;
const thirdOfTriple  = x => y => z => z;

// triple with 3 numbers
const tripleOfNumbers = triple(1)(2)(3);

tripleOfNumbers( firstOfTriple  );    // 1
tripleOfNumbers( secondOfTriple );    // 2
tripleOfNumbers( thirdOfTriple  );    // 3
const Blackbird = f => g => x => y => f(g(x)(y));
const add = x => y => x + y;
const multiplyWithTwo = x => x * 2;

Blackbird(multiplyWithTwo)(add)(2)(3);      // 10
Blackbird(multiplyWithTwo)(add)(10)(20);    // 60
hashtag
get-Präfix

Funktionen die mit einem get beginnen, geben wenn möglich den gewünschten Wert ansonsten ein undefined zurück.

Funktion: getXYZ Ergebnis: Wert oder undefined

Beispiele: getElementByIndex, getIndexOfElement, getDomElementarrow-up-right, getDomElementsarrow-up-right

hashtag
maybe-Präfix

Funktionen die mit einem maybe beginnen, geben im Erfolgsfall ein Just mit den gewünschten Wert, ansonsten ein Nothing zurück.

Funktion: maybeXYZ Ergebnis: Just(Wert) oder Nothing

Beispiele: maybeDivision, maybeTruthy, maybeDomElement, maybeNumber

hashtag
either-Präfix

Funktionen die mit einem either beginnen, geben im Erfolgsfall ein Right mit dem Resultat, ansonsten ein Left mit einer Fehlermeldung zurück.

Funktionen: eitherXY Ergebnis: Left(Fehlerbehandlung) oder Right(Wert)

Beispiele: eitherTruhty, eitherNotNullAndUndefined, eitherDomElement, eitherNumber, eitherFunction

hashtag
Variablen Deklaration

Alle Konstruktionen sind mit dem Keyword const definiert. Somit können diese Variablen nicht überschrieben/verändert werden.

hashtag
Konzepte

hashtag
Konstante Konstrukte

Bei Konstruktionen soll darauf geachtet werden, dass diese aus reinen Funktionen bestehen.

hashtag
Die Brücke zwischen λ und JS

Objekte und Arrays werden nicht verwendet. Ausnahme gibt es bei Funktionen, die als Brücke zwischen den Welten Lambda Kalkül und JavaScript dienen. Das sind die Convert-Funktionen:

  • convertArrayToStack

  • converStackToArray

  • converElementsToStack

Für die Zahlen die Transformation-Funktionen zwischen Church- und JavaScript-Zahlen:

  • jsNum

  • churchNum

hashtag
Formatierung

Bei den erstelleten Funktionen kommt es häufig vor, dass die Impementation aus einer einzigen Codezeile besteht. Die Leserlichkeit kann zum Teil darunter leiden weil die Zeile zu lang ist. Richtiges formatieren der Funktionen mit Zeilenumbrüchen, Einrückungen und Leerzeichen sind daher empfehlenswert und JavaScript ist dabei ziemlich unempfindlich. So darf der Code schön arrangiert werden, denn gut ausgerichteter Code fördert die Leserlichkeit immens.

hashtag
Workflow-Beispiel

Gegeben ist ein nicht formatierter Code: Ein Observable mit ein paar Listener, die hinzugefügt werden. Es ist schwer auf einem Blick zu sehen wieviel und welche Listener es sind, da sie in einer Reihe aufgelistet sind.

hashtag
Schritt 1: Zeilenumbrüche

Wir sind gewohnt das Code Zeilen linksbündig ausgerichtet sind. Diese Struktur wird hier neu definiert. Wenn bei einer Funktion es zu mehrere Funktionsverknüpfungen mit Wertübermittlung kommt, ist es empfehlenswert diese Aufrufe untereinander zu schreiben.

hashtag
Schritt 2: Einrücken

Einrücken der Funktion unterhalb der Haupt-Funktion in einer Linie plus einem Leerzeichen, macht es erkennbarer, dass sie zueinander gehören und darauf aufbauen.

hashtag
Schritt 3: Leerzeichen (Padding)

Es ist schöner und lesbarer, wenn es zwischen den Werten in den Klammern mindestens ein Leerzeichen gibt. Somit bekommen alle Werte dieselbe Präsenz. Es ist dabei empfehlenswert die Klammern auf einer Linie untereinander zu bringen.

hashtag
Schritt 4: Semikolon

JavaScript versucht zwar selber eine Semikolon am Ende einer Anweisung einzufügen, wenn der Programmierer keine gesetzt hat. Hier ist aber nicht klar, ob die Anweisung für JavaScript fertig ist, denn es wäre mittels Funktionskomposition möglich immer weitere Funktionen anzufügen. Es ist darum besser immer ein Semikolon zu setzen, nicht nur um JavaScript zu signalisieren, dass es hier zu ende ist, sondern auch für die Leserlichkeit.

hashtag
Vergleichsbeispiele

hashtag
Listeners-Deklaration

hashtag
ForEach

hashtag
Box

hashtag
JS Doc

Das Dokumentieren der Funktionen mit der JSDocarrow-up-right bringt einige Vorteile. In den ersten Zeilen steht ein Text mit zwei bis drei Sätze, der fachlich erklärt was die Funktion tut. Anschliessend wird mit den JSDoc-Tags die Dokumentation mit Hinweisen erweitert:

  • @haskell Typ deklaration in Haskell Notation

  • @sideffect wenn die Funktion einen Side-Effekt auslöst wie zum Beispiel ein Log auf die Konsole

  • @function markiert eine Funktion explizit als eine Funktion. Optional: Kann man der Funktion einen zweiten Name geben (Alias)

  • @param für das erste Argument (hilfreich für die Pop-Up Informationen)

  • @return wenn die Funktion mehrere Argumente/Funktionen erwartet (hilfreich für die Pop-Up Informationen)

  • @example Beispiele wie die Funktion angewendet wird

Beispiel JS-Dokumentation an der Funktion getElementByIndex

In der IDEA (hier Intellij) wird die Dokumentation dementsprechend angezeigt:.

Dokumentation in der IDEA

Ein sehr praktischer Vorteil, nebst der Dokumentation, sind die Pop-Up Informationen welche dem Anwender beim benutzen der Funktionen angezeigt werden. Der Anwender wird informiert, welcher Parameter als nächstes erwartet wird.

Erste Parameter-Info
Zweite Parameter-Info
HttpGet arrow-up-right
setValue
AllTest.htmlarrow-up-right
Identitätsfunktion
Testsuite - Stack
Einzelner Testfall einer Funktion
Ein fehlgeschlagener Test im Testfall, der Identitätsfunktion
Ausschnitt aus der Gesamtübersicht von allen Test's

Maybe

Villeicht ist ein Wert vorhanden

hashtag
Beschreibung

hashtag
Maybe Type

Der Maybe Type baut auf dem Either Type auf und kommt aus der Welt der funktionalen Programmiersprachen. Der Maybe Type ist ein polymorpher Typ, der auch (wie der Either Type) zwei Zustände annehmen kann. Die Zustände sind: Es existiert ein Wert, dass wird mit Just(value) ausgedrückt oder es existiert kein Wert, dass wird mit Nothing ausgedrückt.

hashtag
Beispiel Szenario:

Wenn eine Funktion, in einer übergebenen Datenstruktur ein Element anhand einer bestimmten Eigenschaft sucht und ein solches Element existiert nicht, dann kann diese Funktion ein Nothing zurückgeben. Dies hat mehrere Vorteile: Der Anwender weiss zu Beginn, dass diese Funktion nur "vielleicht" einen Wert zurück liefert und ist somit auch gezwungen, den Fall zu berücksichtigen wenn kein Wert vorhanden ist.

Durch den Maybe Type kann eleganter auf fehlende, abwesende Werte reagiert werden und dies nur mit Hilfe von reinen Funktionen ohne Seiteneffekte.

hashtag
Maybe Implementation:

Anhand der Implementation von Just und Nothing ist erkennbar, dass der Maybe Type auf dem Either Type basiert. Just ist der Fall bei dem ein Wert vorhanden ist. Dem Just "Konstruktor" kann ein Wert übergeben werden. Nothing ist der Fall bei dem kein Wert vorhanden ist.

hashtag
Verwendung

hashtag
Allgemeine Anwendung für Funktionen, die ein maybe zurückgeben

Bei Funktionen, die ein Maybe zurückgeben können an den Funktionsaufruf zwei weitere Parameter übergeben werden. Der erste Parameter ist eine Funktion, die keinen Parameter entgegen nimmt und den Nothing Fall behandelt. Der zweite Parameter ist eine Funktion für den Just Fall, die das Resultat entgegen nimmt.

hashtag
Allgemeines Schema:

Eine Maybe Funktion XYZ wird mit einem oder mehreren Parametern aufgerufen. Am Schluss vom Funktionsaufruf werden zwei Funktionen übergeben. Eine Funktion für den Nothing Fall und eine für den Just Fall.

hashtag

Die getOrDefault Funktion erwartet ein Maybe und einen Default-Wert. Der Default-Wert wird zurückgegeben falls maybe von Typ Nothing ist.

hashtag

Die Funktion maybeDivision führt 'vielleicht' eine Division mit zwei übergeben Parametern durch. Falls die übergeben Zahlen vom Typ Integer sind und der Divisor nicht 0 ist, wird die Division durchgeführt und es wird Just mit dem Resultat zurückgegeben.

hashtag
Demo maybeDivision Example

hashtag

Diese Funktion nimmt einen Wert entgegen und überprüft ob dieser 'truthy' ist. Falls nicht wird ein Nothing zurückgegeben.

circle-info

.

hashtag

Die maybeNotNullAndUndefined ****Funktion erwartet einen Wert und überprüft ob dieser nicht null oder undefined ist.

hashtag

Diese Funktion nimmt eine Id für ein Dom-Element entgegen. Wenn ein Element mit dieser Id im DOM existiert wird ein Just mit diesem HTML-Element zurückgegeben ansonsten Nothing.

hashtag

Diese Funktion nimmt einen Wert entgegen und prüft ob dieser vom Typ Integer (JavaScript-Zahl) ist. Falls es sich nicht um ein Wert vom Typ Integer handelt wird ein Nothing zurückgegeben.

hashtag

Die maybeFunction Funktion überprüft ob ein Wert vom Typ function ist.

hashtag
Mapping von Maybe

hashtag

Die Funktion mapMaybe wird verwendet um ein Maybe Type zu mappen. Die Funktion nimmt ein Maybe und eine mapping Funktion f entgegen. Die Funktion liefert das gemappte Maybe zurück.

hashtag

Die Funktion flatMapMaybe wird verwendet um eine Maybe Type zu mappen und anschliessend das Resultat abzuflachen.

const textInputObservables = Observable("")(addListener)(listenerNewValue)(addListener)(listenerOldValue)(addListener)(listenerNewValueSize)(addListener)(listenerConsoleLog)
const textInputObservables = Observable("")
(addListener)(listenerNewValue)
(addListener)(listenerOldValue)
(addListener)(listenerNewValueSize)
(addListener)(listenerConsoleLog)
const textInputObservables = Observable("")
                              (addListener)(listenerNewValue)
                              (addListener)(listenerOldValue)
                              (addListener)(listenerNewValueSize)
                              (addListener)(listenerConsoleLog)
const textInputObservables = Observable("")
                              (addListener)( listenerNewValue     )
                              (addListener)( listenerOldValue     )
                              (addListener)( listenerNewValueSize )
                              (addListener)( listenerConsoleLog   )
const textInputObservables = Observable("")
                              (addListener)( listenerNewValue     )
                              (addListener)( listenerOldValue     )
                              (addListener)( listenerNewValueSize )
                              (addListener)( listenerConsoleLog   );
// unformatiert
const listenerNewValue = newListener(listenerNewValueToDomElementTextContent(newValue));
const listenerOldValue = newListener(listenerOldValueToDomElementTextContent(oldValue));
const listenerNewValueSize = newListener(listenerNewValueLengthToElementTextContent(sizes));
const listenerConsoleLog = newListener(listenerLogToConsole);

// formatiert
const listenerNewValue     = newListener( listenerNewValueToDomElementTextContent    (newValue) );
const listenerOldValue     = newListener( listenerOldValueToDomElementTextContent    (oldValue) );
const listenerNewValueSize = newListener( listenerNewValueLengthToElementTextContent (sizes)    );
const listenerConsoleLog   = newListener( listenerLogToConsole                                  );
// unformatiert
forEach(jokes)((joke, _) => getElementByKey(joke)("btn").onclick = _ => HttpGet(getElementByKey(joke)("url"))(resp => jokePairObserver(setValue)(Box(resp)(mapf)(JSON.parse)(fold)(x => pair(getElementByKey(joke)("name"))(x[getElementByKey(joke)("jsonKey")])))));

// formatiert
forEach(jokes)( (joke, _) =>
    getElementByKey(joke)("btn").onclick = _ =>
        HttpGet( getElementByKey(joke)("url") )( resp =>
            jokePairObserver(setValue)( Box(resp)
                                         (mapf)( JSON.parse )
                                         (fold)( x => pair( getElementByKey(joke)("name") )( x[getElementByKey(joke)("jsonKey")] )))));
// unformatiert
const nextCharForNumberString = str => Box(str)(chain)(s => Box(s)(mapf)(s => s.trim()))(mapf)(r => parseInt(r))(mapf)(i => i + 1)(mapf)(i => String.fromCharCode(i))(fold)(c => c.toLowerCase())

// formatiert
const nextCharForNumberString = str =>
    Box(str)
     (chain)(s => Box(s)
                   (mapf)(s => s.trim()))
     (mapf)(r => parseInt(r))
     (mapf)(i => i + 1)
     (mapf)(i => String.fromCharCode(i))
     (fold)(c => c.toLowerCase());
/**
 * A function that takes a stack and an index (as Church- or JS-Number).
 * The function returns the element at the passed index or undefined incl. a Error-Log to the Console
 *
 * @haskell getElementByIndex :: stack -> number -> a
 * @sideeffect Logs a error if index is no Church- or JS-Number or valid number
 * @function
 * @param  {stack} stack
 * @return {function(index:churchNumber|number) : * } stack-value or undefined when not exist or invalid
 * @example
 * const stackWithNumbers  = convertArrayToStack([0,1,2]);
 *
 * getElementByIndex( stackWithNumbers )( n0 ) === id
 * getElementByIndex( stackWithNumbers )( n1 ) ===  0
 * getElementByIndex( stackWithNumbers )( n2 ) ===  1
 * getElementByIndex( stackWithNumbers )( n3 ) ===  2
 *
 * getElementByIndex( stackWithNumbers )( 0 ) === id
 * getElementByIndex( stackWithNumbers )( 1 ) ===  0
 * getElementByIndex( stackWithNumbers )( 2 ) ===  1
 * getElementByIndex( stackWithNumbers )( 3 ) ===  2
 *
 * getElementByIndex( stackWithNumbers )( "im a string" ) === undefined // strings not allowed, throws a Console-Warning
 */
const getElementByIndex = stack => index =>
    eitherElementByIndex(stack)(index)
     (console.error)
     (id);
HttpGet(jokeUrl)
 (response => getDomElement("joke").textContent = JSON.parse(response).value);
assert.equals(1, 1);
lambdaCTest.add("identity", assert => {
    assert.equals(id(1), 1);
    assert.equals(id(n1), n1);
    assert.equals(id(true), true);
    assert.equals(id(id), id);
    assert.equals(id === id, true);
});
getOrDefaultarrow-up-right
maybeDivisionarrow-up-right
maybeTruthyarrow-up-right
Liste mit JavaScript 'falsy' Wertenarrow-up-right
maybeNotNullAndUndefinedarrow-up-right
maybeDomElementarrow-up-right
maybeNumberarrow-up-right
maybeFunctionarrow-up-right
mapMaybearrow-up-right
flatMapMaybearrow-up-right
converObjectToListMaparrow-up-right
convertListMapToArrayarrow-up-right
https://mattwolf-corporation.github.io/ip6:lambda-calculus-in-js/src/observable/observableExamples/observableHttpGetJokeExample/viewObservableHttpGetJokeExample.htmlmattwolf-corporation.github.iochevron-right
https://mattwolf-corporation.github.io/ip6:lambda-calculus-in-js/src/observable/observableExamples/observableColorPickerExample/viewColorPickerExample.htmlmattwolf-corporation.github.iochevron-right
// either type
const Left   = x => f => _ => f (x);
const Right  = x => _ => g => g (x);

// maybe type
const Nothing  = Left();
const Just     = Right ;
// Anwendung        
maybeXYZ(someParam)
 ( ()     => doSomethingInNothingCase(error) )    // Nothing Case
 ( result => doSomethingInJustCase(result)   )    // Just Case
// Implementation
const getOrDefault = maybe => defaultVal =>
    maybe
        (() => defaultVal)
        (id);
   
// Anwendung     
getOrDefault ( Just(10) )(20) // 10
getOrDefault ( Nothing  )(20) // 20
const maybeDivision = dividend => divisor =>
    Number.isInteger(dividend) &&
    Number.isInteger(divisor)  &&
    divisor !== 0
        ? Just(dividend / divisor)
        : Nothing;
const maybeTruthy = value =>
    eitherTruthy(value)
     (_ => Nothing)
     (_ => Just(value));
const maybeNotNullAndUndefined = value =>
    eitherNotNullAndUndefined(value)
     (_ => Nothing)
     (_ => Just(value));
const maybeDomElement = elemId =>
    eitherDomElement(elemId)
     (_ => Nothing)
     (e => Just(e));
const maybeNumber = val =>
    eitherNumber(val)
     (_ => Nothing)
     (_ => Just(val));
const maybeFunction = value =>
    eitherFunction(value)
     (_ => Nothing)
     (_ => Just(value));
// Implementation
const mapMaybe = maybe => f => maybe (() => maybe) (x => Just(f(x)));

// Anwendung
mapMaybe( Just(10) )(x => x * 4) // Just (40)
mapMaybe( Nothing  )(x => x * 4) // Nothing
// Implementation
const flatMapMaybe = maybe => f => maybe (() => maybe) (x => f(x));

// Anwendung
flatMapMaybe( Just(10) )(num => Just(num * 2));    // Just (20)
flatMapMaybe( Just(10) )(num => Nothing      );    // Nothing
flatMapMaybe( Nothing  )(num => Just(num * 2));    // Nothing
https://mattwolf-corporation.github.io/ip6:lambda-calculus-in-js/src/observable/observableExamples/observableTextInputExample/viewTextInputExample.htmlmattwolf-corporation.github.iochevron-right
https://mattwolf-corporation.github.io/ip6:lambda-calculus-in-js/src/maybe/maybeExample/maybeExample.htmlmattwolf-corporation.github.iochevron-right