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 mit ES6
Für nicht ganz aktuelle Browser: → Quicksort-Demo mit ES5

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

In den letzten 20 Jahren ist Java-Script ständig weiterentwickelt worden. Mit dem in LOG-IN 5 '97 beschriebenen Java-Script haben die aktuellen Versionen ES5 und ES6 nur noch wenig zu tun. ... WEITERES FOLGT ...

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 seit ECMASript 5.1.
["🍔", "🍕", "🍰"].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 – Wozu JavaScript?

"Worin besteht der bildende Wert textbasierter Programmierung, also der Möglichkeit, Syntaxfehler machen zu können, wenn inhaltlich alle informatischen Konzepte, aber auch Standardanwendungsbereiche und -aufgaben mit grafischen Programmierwerkzeugen (wie z.B. BYOB) realisiert werden können? Ist die mit der Textbasierung verbundene Frustrationsrate gerechtfertigt, hat Syntax ihren eigenen Wert?", fragen Modrow, Mönig und Strecker in "Wozu JAVA?" in LOG IN Heft Nr. 168 (2011)


Böttcher, K.: JAVA jetzt – adieu PASCAL. Zur Evolution des Informatikunterrichts. In: LOG IN 17 (1997) Heft 6, S. 38–45

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

Koerber, B.: Haben Sie noch ELAN? Eine kleine Geschichte der Programmiersprachen in der Schule. In: LOG IN, 35. Jg. (2015), Nr. 181/182, S. 10–28

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.

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.

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


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.

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


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


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



Ideen für später


// Man kann gar nicht genug Danke sagen!
const danke = (n) => n > 0
    ? "Danke, " + danke(n - 1)
    : "Danke!";

const fibFolge = (n, reihung = [1, 1]) =>
    reihung[1] + reihung[0] >= n
    ? reihung
    : fibFolge(n, [reihung[0]+reihung[1]].concat(reihung));

fibFolge(34) // [21, 13, 8, 5, 3, 2, 1, 1]
fibFolge(35).reverse() // [1, 1, 2, 3, 5, 8, 13, 21, 34]

/////////////////////////////////////////////////////////////////
const fibMax = (ziel, f1 = 1, f2 = 1) =>
// fibMax ist größte Fibonaccizahl, die nicht größer als ziel ist
     f2 > ziel
     ? f1
     : fibMax(ziel, f2, f1 + f2);

console.log(fibMax(54), fibMax(55), fibMax(56)) // 34 55 55

var zeckendorf = (zahl, zSequenz = []) => {
// Zeckendorfsequenz von zahl erzeugen
    const max = fibMax(zahl);
    zSequenz = zSequenz.concat(max);
    return max === zahl
         ? zSequenz
         : zeckendorf(zahl - max, zSequenz)
}

zeckendorf(1e6) // [832040, 121393, 46368, 144, 55]
/////////////////////////////////////////////////// // Übung: Zahlen mittels Binärbaum sortieren const einfuegen = (element, baum) => { if (baum.inhalt === undefined) { baum.inhalt = element; baum.links = {}; baum.rechts = {}; } else { if (element < baum.inhalt) { einfuegen(element, baum.links); } else { einfuegen(element, baum.rechts); } } }; const erzeugeBaum = (n, baum = []) => { let reihung = []; for (let i = 0; i < 12; i++) { let n = Math.round(Math.random()*90 + 10); reihung.push(n); einfuegen(n, baum); }; console.log(...reihung); return baum; }; const zeigeBaum = (baum, t = '') => { if (baum.inhalt !== undefined) { zeigeBaum(baum.rechts, t + '--'); console.log(t + baum.inhalt); zeigeBaum(baum.links, t + '--'); }; }; const traversiereBaum = (baum, accu = []) => { if (baum.inhalt !== undefined) { accu = traversiereBaum(baum.links, accu); accu = accu.concat(baum.inhalt); accu = traversiereBaum(baum.rechts, accu); }; return(accu); }; let baum = erzeugeBaum(12); zeigeBaum(baum); traversiereBaum(baum); /* Ausgabe: 69 51 49 73 70 54 15 68 73 12 28 58 ----73 --73 ----70 69 ------68 --------58 ----54 --51 ----49 --------28 ------15 --------12 [12, 15, 28, 49, 51, 54, 58, 68, 69, 70, 73, 73] *////////////////////////////////////////////////////////////////

HTML-Code in HTML darstellen

Um HTML-Code auf einer Seite darzustellen, müssen die HTML-eigenen Zeichen <, >, & und "
durch die Zeichenfolgen &lt;, &gt;, &amp; und &quot; ersetzt werden.
Die Umlaute (&auml; statt ä) müssen nicht ersetzt werden, wenn im head-Bereich <meta charset="utf-8"> steht.





<textarea id='TA1'> </textarea>

<button type='button'
   onclick = '
     "use strict";
     var eingabe = document.getElementById("TA1").value;
     var ausgabe = eingabe.replace(/&/g, "&\amp;")
                   .replace(/</g, "&\lt;")
                   .replace(/>/g, "&\gt;")
                   .replace(/"/g, "&\quot;");
     document.getElementById("TA2").value =
                               "<pre><code>\n"
                               + ausgabe
                               + "\n</code><pre>";
  '>HTML-Code konvertieren</button>

<button type="button"
  onclick = "document.getElementById('TA1').value = ''";
> Eingabe löschen </button>

<textarea id='TA2'> </textarea>
siehe Swift Implementation von Joey Devilla
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
};