Statt über Smalltalk zu berichten, erscheint hier zunächst ein Artikel über die Entwicklung von JavaScript und seine funktionalen Sprachelemente


ES6 – Die Javascript-Revolution

Atwood's Law:
any application that can be written in JavaScript,
will eventually be written in JavaScript.
blog.codinghorror.com/the-principle-of-least-power
Tim Berners-Lee's Law: → "The Rule of Least Power"

49 „Programmiersprachen bzw. –systeme, die in der Schule eingesetzt wurden“ hat Bernhard Koerber seiner „kleinen Geschichte der Programmiersprachen in der Schule“ in einer Übersicht aufgezählt. Dabei ist eine Sprache übersehen worden, die bereits mehrfach in LOG IN behandelt wurde, die jeder Schüler zumindest passiv benutzt und die im Ranking der Programmiersprachen, die in Stellenanzeigen in den USA, GB und Australien am meisten gesucht werden und noch vor JAVA auf Platz 1 steht: die Lingua franca des Internets.

Die Tabelleneinträge seien hier nachgeholt:
Name: LiveScript, JavaScript, ECMA-Script3, ES5, ES6/ES2015.
Bedeutung des Namens: Der Name ist eine reine Marketingentscheidung.
Jahr der Einführung: 1996
Entwickler: Brendan Eich.
Merkmale: Prototypbasiertes Objektsystem mit funktionalen Elementen


JavaScript war von Anfang an als funktionale Programmiersprache angelegt. Im April 1995 wurde Brendan Eich von Netscape mit dem Argument angeworben, "den Browser um (das funktionale) Scheme zu erweitern" [Hug, 2012]. "Um alle Zweifler ... zu überzeugen", schreibt Eich, "musste ich innerhalb von zehn Tagen eine Demoversion erstellen Ich arbeitete Tag und Nacht und baute daher ganz unvermeidlich ein paar Designfehler in die Sprache ein (die aus der Evolution von LISP übernommen wurden)".

Registermaschine
Bild 1: → Registermaschine
4 Stifte gleichzeitig
Bild 2: → Turtelgrafik mit drei Stiften
Quicksort funktional
Bild 3: → Quicksort-Demo

"JavaScript wird eines Tages untergehen", sagt Bredan Eich (vergl. Seibel, 2010), plädiert aber zugleich für eine ständige Verbesserungen seiner Programmiersprache.

Einige Ausdrucksmöglichkeiten von JavaScript sollen anhand dreier Beispielprogramme aus dem Informatikunterricht dargestellt werden.

Registermaschine

Turtlegrafik

Quicksort

Mit HTML5 hat sich das Erstellen einer validen HTML-Seite sehr vereinfacht. Bild 1 zeigt ein mögliches Ergebnis mit Ein- und Ausgabemöglichkeit. Aber es geht noch viel einfacher, wenn man auf HTML ganz verzichtet und einfach die Funktionsdeklaration im <script>-Attribute in eine Browser- oder Node.js-Konsole eingibt.



Vererbung: klassisch oder modern?

JavaScript ungleich Java

"Java verhält sich zu JavaScript wie Ham zu Hamster", steht es in [MEH, 2013]. Anderere Vergleiche im Internet sind "car und carpet" oder "Ei und Eiffelturm". Die meisten der zahlreichen englisch- oder deutschsprachigen Büchern zu JavaScript ...
Array.prototype.mmap = function (fn) {return this.reduce((a,b) => a.concat(fn(b)), []);};

Emoji-Demonstration
Bild 4: Die Funktionsweise von map, filter und reduce demonstriert mit Emojis
von Joey Devilla mit einer Implementation in Swift

Die Array-Methode reduce gibt es auch in ES6.
["🍔", "🍕", "🍰"].reduce(
    (akku, x) => akku+"💩 ", '');
// "💩 💩 💩 "

// eine vereinfachte Version von reduce
// mit dem ternären Vergleichsoperator (? :)

const reduziere = ([kopf, ...rest], fn, akku) =>
    typeof kopf === 'undefined'
    ? akku
    : reduziere(rest, fn, fn(akku, kopf));

reduziere([2,3,7], (a, b) => a * b, 1); // 42
// map und filter mittels reduziere

const map = (arr, fn) =>
    reduziere(arr, (a, b) => a.concat(fn(b)), []);

map([5,6,7], (x) => x*x); // [25, 36, 49]

const filter = (arr, fn) => reduziere(arr,
   (a, b) => fn(b) ? a.concat(b) : a, []);

filter([5,55,33,4], (x) => x > 10); // [55, 33]

Mit der Pfeilfunktion filter lässt sich nun im deklarativen Stil quicksort formulieren.

const quicksort = ([kopf, ...rest]) =>
    kopf === undefined // Liste leer?
    ? []               // ja, dann []
    : [].concat(       // sonst:
        quicksort(filter(rest, (a) => a < kopf)),
        kopf,
        quicksort(filter(rest, (a) => a >= kopf))
    );

let liste = [5,1,3];
quicksort(liste); // [1, 3, 5]
liste //  [5,1,3] bleibt unverändert

Fazit – JavaScript jetzt?

Crockford, D., Das Beste an JavaScript, Köln: O'Reilly, 2008
Hughes-Croucher, T; Wilson, S.: Einführung in Node.js, Köln: O'Reilly, 2012, S. XI
Seibel, P.: Coders at Work. Bedeutende Programmierer und ihre Erfolgsgeschichten. mitp-Verlag, 2011



Teil 2


ES6 Beispiele

Beispiel 1: Funktionsabschlüsse (Closures)

In der Fabrikfunktion "erzeugePerson" sind zehn Variablen zu unterscheiden, fünf im Funktionsabschluss (vname, name, ort, gibOrt und gibNamen) und fünf Eigenschaften des zurückgegebenen Objekts (vname, name, alter, gibOrt, gibNamen) . Die Variablen name und ort bleiben auch nach dem Beenden der Funktion erzeugePerson in einem Funktionsabschluss (Closure) erhalten und zwar für jedes Personenobjekt getrennt. Das gilt allerdings nur solange wie es die privilegierten Funktionen (gibOrt, gibNamen) gibt , die auf sie zugreifen. Mit der Anweisung "person1.gibNamen = function () {return this.name;};" wäre die Variable name im Funktionsabschluss nicht mehr erreichbar (und würde bei einer Speicherbereinigung entfernt werden), sondern nur die Eigenschaft name.

let erzeugePerson = (vname, name) => {
  let ort = "Buxtehude";
  let gibOrt = () => ort;
  let gibNamen = () => name;
  return {
    "vname": vname,
    "name": name,
    "alter": 0,
    "gibOrt": gibOrt,
    "gibNamen": gibNamen
  };
};

let person1 = erzeugePerson("Grete", "Meier");
let person2 = erzeugePerson("Peter", "Meier");

// einige Tests und ihre Rückgabewerte
console.log(              // Rückgabe:
person1.name,             // 'Meier'
person1.name = "Müller",  // 'Müller'
person1.name,             // 'Müller'
person2.name,             // 'Meier'
person1.gibNamen(),       // 'Meier'
person1.ort = "Berlin",   // 'Berlin'
person1.ort,              // 'Berlin'
person2.ort,              // undefined
person1.gibOrt()          // 'Buxtehude'
);

person1.getName = function () {
    return this.vname + ' ' + this.name;
};

let kind1 = {vname: 'Renate'}

Object.setPrototypeOf(kind1, person1);

kind1.getName();          // 'Renate Müller'

OLOO (objects linked to other objects) statt OOP: Beim Objekt kind1 erkennt man, wie prototypische Verberbung (behavior delegation) ohne Klassen funktioniert. kind1 wird durch ein Literal erzeugt und erbt vom Prototyp person1. Da kind1 keine Methode getName besitzt, wird in der Prototypenkette diese Funktion beim Objekt person1 gefunden. Das this-Objekt von getName ist kind1 und nicht etwa person1. Deshalb ist this.vname "Renate" und this.name ("Müller") wird beim Prototyp gefunden.

Merke:

Eine Pfeilfunktion hat eine kürzere Syntax, besitzt kein eigenes this-Objekt und kann nicht mittels new als Konstruktorfunktion verwendet werden. Verwende Pfeilfunktionen für funktionale Programmierung.

Mit function deklarierte Funktionen werden als Methoden verwendet. Dabei ergibt sich der Wert, der durch this referenziert wird, dynamisch aus dem Aufrufkontext.

Beispiel 2: Pfeilfunktionen und Funktionen als Methoden

Bei den neuen Pfeilfunktionen (wie g) erfolgt die Bindung an das this-Objekt bei der Definition und kann nicht mehr geändert werden, während bei der "normalen" Funktion (wie f) this das Objekt referenziert, von dem aus die Funktion ausgeführt wird.

Die bisherigen Funktionen sollten für Methoden verwendet werden, während die Pfeilfunktionen für die funktionale Programmierung besonders geeignet sind.

var person = {};
var ID = 42; //globale Variable
const f = function () {
    return this.ID;
};
const g = () => this.ID; // this === Window

person.ID = Math.random(); // 0.165700
person.gibID = f;
person.getID = g;
person.gibID(); // 0.165700
person.getID(); // 42

Beispiel 3: Verkettung von Funktionen

const erzeugeMult = (faktor) => (x) => faktor * x;
const mwSteuer19 = erzeugeMult(1.19);
mwSteuer(200); // 238

const erzeugeAdd = (a) => (x) => a +x;
const mitPorto5 = erzeugeAdd(5);
const endPreis = (x) => mitPorto5(mwSteuer19(x));
endPreis(200); // 243

Beispiel 4: Rest-Operator und destrukturierende Zuweisung

Der Rest-Operator (...) erleichtert funktionales Programmieren mit JavaScript außerordentlich. Bei der print-Funktion tritt er zweimal auf. Die Paramterliste wird in der Reihung args gesammelt und die Elemente dieser Reihung werden einzelnd als Parameter in die log-Funktion eingesetzt. Die destrukturierende Zuweisung erspart eine temporäre Variable beim Tausch von x und y und vereinfacht die Aufteilung

const add = (...args) =>
    args.reduce((a, b) => a+b);
add(9, 10, 11, 12); // 42

const print = (...args) =>
    console.log(...args);

let x = 5, y = 8;
[x, y] = [y, x];
print(x, y); // 8 5

let liste = [9, 10, 11];
[kopf, ...rest] = liste;
print(kopf, rest, liste); // 9 [10, 11] [9, 10, 11]


Beispiel 5: Vorbelegte Parameter und Endrekursion

Will man endrekursive Funktionen schreiben, dann benötigt man meistens einen oder mehrere Akkumulatoren (hier vorbelegte Paramerter) zum Speichern von Zwischenergebnissen. Endrekursion wird von künftigen Browsern unterstützt werden.

const fakultaet = (n, akku = 1) =>
    n <= 1
    ? akku
    : fakultaet(n - 1, n * akku);

fakultaet(69) // 1.7112245242814127e+98

const fibonacci = (n, f1 = 1, f2 = 1) =>
     n === 1
     ? f1
     : fibonacci(n-1, f2, f1 + f2);
fibonacci(69) // 117669030460994

Beispiel 6: Quicksort funktional

Mit den neuen Sprachelementen von ES6 lässt sich Quicksort funktional und damit deklarativ ohne zusätzliche Variablen und ohne Seiteneffekte programmieren.

const quicksort = ([kopf, ...rest]) =>
    kopf === undefined // Liste leer?
    ? []               // ja, dann []
    : [].concat(       // sonst:
        quicksort(rest.filter((a) => a < kopf)),
        kopf,
        quicksort(rest.filter((a) => a >= kopf))
    );

quicksort([5,1,3]); // [1,3,5]

// Zeitkomplexität testen:
let test = function (n) {
    "use strict";
    let i, start, reihung = [];
    for ( i = 0; i < n; i += 1 ) {
        reihung.push(Math.random());
    }
    start = Date.now();
    quicksort(reihung); // reihung.sort()
    return (Date.now() - start) ;
};

test(2E6) / test(1E6) // 2.098208770846201

Das passt zur Kostenfunktion T(n) = n*log(n). Aber wie schnell wird sortiert?

test(1E6) // 4776

1000000 Elemente in knapp 5 s. Zum Verleich wird in der Funktion test2 "reihung.sort()" verwendet. sort ist eine eingebaute Arrayfunktion von JavaScript.

test2(1E6) // 5861

Das Ergebnis kann nicht stimmen und den Grund erkennt man bei folgenden Tests:

[3, 1, 12, 5, 1].sort(); // [1, 1, 12, 3, 5]
[3, 1, 12, 5, 1].sort((a, b) => a - b) // [1, 1, 3, 5, 12]
[3, 1, 12, 5, 1].sort((a, b) => b - a) // [12, 5, 3, 1, 1]

Der Array-Methode sort muss eine Vergleichsfunktion als Parameter übergeben werden, ansonsten wird lexikographisch sortiert. Die anonyme Vergleichstfunktion bestimmt im Beispiel, ob die Zahlen auf- oder absteigend sortiert werden. Ein Funktionsaufruf test3(1E6) mit Sortierung nach Größe ergibt damit z. B. 1406 (ms). Auch eine Reihung von Personen kann auf diese Weise sortiert werden, ohne den sort-Algorithmus neu schreiben zu müssen.

[person1, person2].sort((a, b) => a.alter - b.alter);

Beispiel 7: UPN-Terme

let upn = [[[2, 2, "*"], 3, "+"], [2, 4, "+"], "*"];

const plaette = (liste) => liste.reduce( //       *
        (akku, element) => akku.concat(  //     /   \
            Array.isArray(element) ?     //    +     +
            plaette(element) :           //   / \   / \
             element                     //  *   3 2   4
        ),                               // / \
    [] // Startwert für akku               2   2
);

plaette(upn); // [2, 2, "*", 3, "+", 2, 4, "+", "*"]
plaette(upn).join(" "); // "2 2 * 3 + 2 4 + *"

const berechne = (op1, op2, op) => { // Hilfsfunktion
        if (op == "+") return op2 + op1;
        if (op == "-") return op2 - op1;
        if (op == "*") return op2 * op1;
        if (op == "+") return op2 / op1;
    };

berechne(45, 49, "-"); // 4

var berechneUPN = (upnString) =>
        upnString.split(" ")
        .reduce((stapel, element) =>  // stapel als Akkumulator
            ["+", "-", "*", "/"].includes(element) ?
            stapel.concat([berechne(stapel.pop(),  stapel.pop(), element)]) :
            stapel.concat([+element]) // Typwandlung ergibt Zahl
        , [] // beginne mit leerem Stapel
        )[0];

berechneUPN(plaette(upn).join(" ")); // 42
berechneUPN("49 45 - 3 + 2 4 + *") === 42 // true


const danke = (n) => n > 0
    ? "Danke, " + danke(n - 1)
    : "Danke!";

let cookupTable = {
    "🐮": "🍔", // Cow face -> burger
    "🐄": "🍔", // Cow -> burger
    "🐂": "🍖", // Ox -> meat on bone
    "🐷": "🍖", // Pig face -> meat on bone
    "🐽": "🍖", // Pig nose -> meat on bone
    "🐖": "🍖", // Pig -> meat on bone
    "🐑": "🍖", // Sheep -> meat on bone
    "🐐": "🍖", // Goat -> meat on bone
    "🐔": "🍗", // Chicken -> poultry leg
    "🦃": "🍗", // Turkey -> poultry leg
    "🐸": "🍗", // Frog  -> poultry leg (no frog leg emoji...yet)
    "🐟": "🍣", // Fish -> sushi
    "🐠": "🍣", // Tropical fish -> sushi
    "🐡": "🍣", // Blowfish -> sushi
    "🐙": "🍣", // Octopus -> sushi
    "🍠": "🍟", // (Sweet) potato -> French fries
    "🌽": "🍿", // Corn -> popcorn
    "🌾": "🍚", // Rice -> cooked rice
    "🍓": "🍰", // Strawberry -> shortcake
    "🍂": "🍵", // Dried leaves -> tea
};