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


ES6 – Die Javascript-Revolution

Tim Berners-Lee's Law:
"The Rule of Least Power"
Computer Science spent the last forty years making
languages which were as powerful as possible.
Nowadays we have to appreciate the reasons for picking
not the most powerful solution but the least powerful
.
w3.org/2001/tag/doc/leastPower-2006-02-23.html

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

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-Script, ES5, ES6.
Bedeutung des Namens: Der Name ist eine reine Marketingentscheidung.
Jahr der Einführung: 1996
Entwickler: Brendan Eich.
Merkmale: Prototypbasiertes Objektsystem mit funktionalen Elementen


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 (am Sinn einer zweiten Sprache neben Java) zu überzeugen, schreibt Eich, "musste ich innerhalb von zehn Tagen eine Demoversion erstellen (Das entbehrt nicht einer gewissen Ironie, da Java aus den Browsern fast vollständig verschwunden ist, während JavaScript auf der Clientseite die entscheidende Sprache wurde.) 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)".

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

Objekte zuerst?

Registermaschine

Bild 1: Registermaschine

4 Stifte gleichzeitig

Bild 2: Turtelgrafik mit drei Stiften

Quicksort funktional

Bild 3: Quicksort-Demo

Das Schlagwort der OOP lautet "Objekte zuerst!". In JavaScript ist ein Objekt {...} einfach eine Sammlung durch Kommas getrennter Eigenschaften . Eine Eigenschaft (property) besteht aus einem Namen (Typ String) und einem Wert, getrennnt durch einen Doppelpunkt. Funktionen sind Objekte erster Klasse. Ist eine Funktion der Wert einer Eigenschaft, dann wird sie Methode genannt.
Ein einfaches Beispiel:

Ein außerordentlicher Vorteil von JavaScript ist, dass Codestücke sofort getestet werden können. Werden diese Zeilen in die interaktive Node.js-Konsole (REPL = Read-Eval-Print Loop) eingegeben, dann erscheint der Rückgabewert in der nächsten Zeile. Andere Werte können beim Testen mit console.log angezeigt werden. (In einer modernen Browser-Konsole funktioniert es genauso.) Eine Variablendeklaration mit let gibt undefined zurück.
Eine Konsole eignet sich sehr gut zum Testen kleiner Codestücke. So steht hinter dem Funktionsaufruf person2.getOrt() der Kommentar // 'Buxtehude' zurecht, denn der Rückgabewert ist nicht etwa 'Hamburg', sondern dem Objekt person2 wurde eine neue Eigenschaft "ort" mit dem Wert "Hamburg" zugewiesen. Die private Variable ort hingegen befindet sich in einem Funktionsabschluss (Closure) und nur durch die privilegierte Methode getOrt kann auf diese lesend zugegriffen werden. Um die private Variable ort zu verändern, muss die Fabrikfunktion erzeugePerson dem zurück gegebenen Objekt eine (privilegierte) Methode "setOrt": (neuerOrt) => ort = neuerOrt mitgeben. Jedes so erzeugte Objekt hat seine eigene privaten Variablen.

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 ...

Fazit – JavaScript jetzt?

Mit ES6 hat JavaScript erneut an Ausdrucksstärke zugenommen. Der Quicksort-Algorithmus lässt mit Hilfe des Rest-Operators (...) und der Destrukturierung ([kopf, ...rest] = reihung) sowie der Array-Methode filter noch leichter verständlich formulieren:

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

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

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




Drei Bespielprogramme mit ECMAScript 3

Quicksort

Einige Ausdrucksmöglichkeiten von JavaScript sollen anhand dreier Beispiele aus dem Informatikunterricht dargestellt werden. Zuerst soll ein möglichst kurzes Programm entwickelt werden mit dem der Quicksort-Algorithmus in einem Browser gestestet werden kann.

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. Die Konsole wird dann undefined zurückgeben, weil eine Funktionsdeklaration keinen Wert zurück gibt.

In der Konsole kann ganz einfach die Zeitkomplexität getestet werden:

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);
    stopp = Date.now();
    return (Date.now() - start) ;
};

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

Weitere Testläufe zeigen, dass sich die Kostenfunktion offenbar durch T(n) = n*log(n) beschreiben lässt. 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]
[person1, person2].sort((a, b) => a.alter - b.alter);

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.

Registermaschine

Turtlegrafik



ES6 Beispiele

Beispiel 1: Funktionsabschlüsse (Closures)

In der Fabrikfunktion "erzeugePerson" sind zehn Variablen zu unterscheiden, fünf im Funktionsabschluss (vname, name, ort, getOrt und getName) und fünf Eigenschaften des zurückgegebenen Objekts (vname, name, alter, getOrt, getName) . 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 Funktionen gibt (gibOrt, gibNamen), die auf sie zugreifen. Mit der Anweisung "person1.gibNamen = function () {return this.name;};" wäre die Variable name im Funktionsabschluss nicht mehr erreichbar, sondern nur die Eigenschaft name, und kann bei einer Speicherbereinigung entfernt werden. Pfeilfunktionen sind insbesondere bei der funktionalen Programmierung sehr praktisch.

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(person1.name, // 'Meier'
person1.name = "Müller",
person1.name, // 'Müller'
person2.name, // 'Meier'
person1.gibNamen(), // 'Meier'
person1.ort = "Berlin",
person1.ort, // 'Berlin'
person2.ort, // undefined
person1.gibOrt() // 'Buxtehude'
);

Beispiel 1: Funktionsabschlüsse (Closures)

In der Fabrikfunktion "erzeugePerson" sind zehn Variablen zu unterscheiden, fünf im Funktionsabschluss (vname, name, ort, getOrt und getName) und fünf Eigenschaften des zurückgegebenen Objekts (vname, name, alter, getOrt, getName) . 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 Funktionen gibt (gibOrt, gibNamen), die auf sie zugreifen. Mit der Anweisung "person1.gibNamen = function () {return this.name;};" wäre die Variable name im Funktionsabschluss nicht mehr erreichbar, sondern nur die Eigenschaft name, und kann bei einer Speicherbereinigung entfernt werden. Pfeilfunktionen sind insbesondere bei der funktionalen Programmierung praktisch, wie die folgenden Beispiele zeigen.

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

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: 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


var 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]