Funktionale Programmierung mit JavaScript



Funktionale Programmierung mit JavaScript

1 0


afp-js


On Github mihaeu / afp-js

Funktionale Programmierung mit Javascript - FH Rosenheim - AfP SS 2016 - Michael Haeuslmann & Marinus Noichl

Funktionale Programmierung mit JavaScript

λ
1. April: Michael Häuslmann 15. April: Marinus Noichl

Quellen & Source- und Beispielcode

Github Repository

https://github.com/mihaeu/afp-js

Agenda

  • Geschichte & Herkunft
  • Sprachgrundlagen & Eigenschaften
  • Funktionale Konzepte
  • Anwendungsbereiche
  • Beispiele

Dauer: ~45min (exkl. Fragen)

Geschichte & Herkunft

Wer ist an der Hochschule bereits mit JavaScript in Berührung gekommen?

Wer arbeitet privat/beruflich mit JavaScript?

Wer meint JavaScript ist eine „schöne“ Sprache?

Geschichte & Herkunft

[...] you have the power to define your own subset. You can write better programs by relying exclusively on the good parts. - Douglas Crockford, JavaScript - The Good Parts

Geschichte & Herkunft

  • 1995 von Netscape entwickelt (in 10 Tagen) für deren Browser
  • verschiedene Implementierungen u.a. von Microsoft (JScript)
  • Sprachstandard erst später entstanden (ECMAScript)
  • aktueller Standard ist ES6 und wird von den meisten modernen Browsern zum Teil implementiert
  • viele Runtime Engines, Cross-Compiler und Super-Sets (GWT, TypeScript, Dart, Coffeescript, ClojureScript...)
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.Window;

public class HelloWorld implements EntryPoint {
    public void onModuleLoad() {
        Window.alert("Hello, World!");
    }
}

Python + Ruby + Haskell = ♥

# Conditions:
number = -42 if opposite

# Functions:
square = (x) -> x * x

# Splats:
race = (winner, runners...) ->
print winner, runners

# Array comprehensions:
cubes = (math.cube num for num in list)
class Greeter {
    constructor(public greeting: string) { }

    greet() {
        return "<h1>" + this.greeting + "</h1>";
    }
};

var greeter = new Greeter("Hello, world!");

document.body.innerHTML = greeter.greet();

Geschichte & Herkunft

  • Keine (ernsthaften) Alternativen
  • früher außschließlich im Frontend, jetzt überall (Backend, Desktop- und Mobileapps)
  • als Java Umgebung fürs Backend (mit V8 von Google als Runtime)
  • unsere Meinung: sehr viel Hype, aber auch echte Chancen
- die einzige Sprache die auf jedem eurer Geräte von Anfang an interpretiert werden kann

Agenda

  • Geschichte & Herkunft
    • kommt aus dem Web
    • jetzt überall
    • viele Altlasten
    • sehr aktive und beliebt
  • Sprachgrundlagen & Eigenschaften
  • Funktionale Konzepte
  • Anwendungsbereiche
  • Beispiele

Sprachgrundlagen & Eigenschaften

var schlafEin = (anzahlSchafe) => {
  var i = 0, ausgabe = [];
  for (i = anzahlSchafe; i > 0; --i) {
    if (i === 1) {
      ausgabe.push(1 + " Schaf");
    } else {
      ausgabe.push(i + " Schafe");
    }
  }
  return ausgabe.join("\n") + "\nZzzz ...";
};
schlafEin(5);

Sprachgrundlagen & Eigenschaften

Datenstrukturen

// boolean
true
false

// number
1
3.1415
(3.1415).toString()

// string
'Hello'

// regex
/java[sS]cript/
Alles ist ein Objekt (auch Funktionen selbst)!

Sprachgrundlagen & Eigenschaften

Datenstrukturen

// assoziatives Array (functioncal scope)
var arr = [1, 2, 3];

// Objekte ähnlich wie JSON (global scope)
obj = {
    bezeichner: 'wert'
}

// seit ES6: Map und Set
// immutable Map (block scope)
const map = new Map([[ 1, 'one' ]]);

// Set (block scope)
let set = new Set([1, 1, 1, 2]); // Set { 1, 2 }

Sprachgrundlagen & Eigenschaften

  • Interpretiert
  • Schwache dynamische Typisierung
  • Type Coercion
  • Lexikalisches Scoping auf Funktionsebene
  • Prototyp orientiert

Schwache dynamische Typisierung

// praktisch: kein List<Integer> list = new ArrayList<Integer>();
let list = [1, 2, 3, 4, 5];

// dynamisch typisiert, keine Initialisierung notwendig
let organization = 'Microsoft';

// ⚡ neue Variable durch Schreibfehler ⚡
organisation = 'Google';
// schwach typisiert
let x = 1;                          // number
x = true;                           // boolean
x = 'true';                         // string

⚡ Abgesehen für kleine Skripte fast nur Nachteile ⚡

Type Coercion

0 == ''           // true

0 == '0'          // true

'' == '0'         // false


false == 'false'  // false

false == '0'      // true

" \t\r\n " == 0   // true

⚡ Typumwandlungen z.B. bei == != + ... ⚡

♥ immer === verwenden! ♥

Lexikalisches Scoping auf Funktionsebene

var x = 3;
function func(randomize) {
    var x;                  // geht nur da functional scope
    if (randomize) {        // bei let error
        let x = Math.random();
        return x;
    }
    return x;
}
func(false);                // undefined

♥ immer let verwenden! ♥

Prototyp orientiert

  • Keine Klassen, Methoden, Konstruktoren, Module (zumindest vor ES6)
  • Aber alles über Prototypen möglich
var Animal = (function() {
  function Animal(name) {
    this.name = name;
  }

  Animal.prototype.move = function(meters) {
    return this.name + " moves " + meters + "m.";
  };

  return Animal;
})();

Prototyp orientiert

Inheritance :(
function Snake(name, isPoisonous) {
  Animal.call(this, name); // super(name)
  this.isPoisonous = isPoisonous;
}

Snake.prototype = Object.create(Snake.prototype);
Snake.prototype.constructor = Snake;
Snake.prototype.move = function (meters) {
  return this.name + " wiggles " + meters + "m.";
};

Prototyp orientiert

(under the hood)

  • Seit ES6 viel syntaktischer Zucker:
class Animal {
  constructor(name) {
    this.name = name;
  }
  move(meters) {
    return this.name + " moves " + meters + "m.";
  }
}

class Snake extends Animal {
  move(meters) {
    return this.name + " wiggles " + meters + "m."
  }
}

Agenda

  • Geschichte & Herkunft
  • Sprachgrundlagen & Eigenschaften
    • interpretiert & schwach dynamisch typisiert
    • Prototyp-orientiert
    • viele Fallen
    • knappe Schreibweise
  • Funktionale Konzepte
  • Anwendungsbereiche
  • Beispiele

Funktionale Konzepte

The good ♥

  • Funktionen waren schon immer ein first-class-citizen
  • seit ES6 Tail Recursion!
  • eingebaute Funktionen höherer Ordnung: filter, reduce, map (aber teils untypische Implementierungen)

The bad ⚡

  • keine Lazy Evalution (aber es gibt Libraries)
  • viele Seiteneffekte
  • kein Currying, Pattern Matching, ...

Höhere Funktionen in JavaScript: filter()

Welcher Ansatz ist einfacher zu verstehen und hat weniger mögliche Fehlerquellen?
// imperative
var data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for (var i = 0, result = []; i < data.length; i++) {
  if (data[i] % 2 === 0) {
    result.push(data[i]);
  }
}
return result;


// functional
return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].filter(i => i % 2 === 0);

Höhere Funktionen in JavaScript: map()

Was mache ich, wenn ich eine ähnliche Funktion wie z.B. die Quadratwurzel brauche?
// imperative
data = [0, 1, 2, 3];
for (var i = 0, result = []; i < data.length; i++) {
  result.push(data[i] * data[i]);
}
return result;


// functional
return [0, 1, 2, 3].map(i => i * i);

Höhere Funktionen in JavaScript: reduce()

// imperative
let sum = 0;
for (const i of [1, 2, 3, 4]) {   // neues immutable for seit ES6
  sum += i;
}
return sum;


// functional
return [1, 2, 3, 4].reduce((i, j) => i + j);

Agenda

  • Geschichte & Herkunft
  • Sprachgrundlagen & Eigenschaften
  • Funktionale Konzepte
    • funktionale Programmierung möglich
    • viel Handarbeit oder Erweiterungen nötig
    • Performanz an manchen Stellen problematisch
  • Anwendungsbereiche
  • Beispiele

Anwendungsbereiche

  • Web Frontend, Server Backend, Mobile- & Desktopapps: Templating, APIs, DOM, DB Operationen, ...
  • viele Sachen laufen nebenbei
  • JavaScript Umgebungen arbeiten aber mit einem einzigen Thread
  • daher keine Möglichkeit zur Parallelisierung
  • aber JavaScript Umfeld (Web) und Node Architektur Event-basiert

Anwendungsbereiche

Kurzer Ausflug: Node Architektur

The cost of IO

L1-Cache 3 cycles L2-Cache 14 cycles RAM 250 cycles Disk 41 000 000 cycles Network 240 000 000 cycles
  • Forks & Threads teuer
  • oft Probleme
  • synchron zu langsam

Anwendungsbereiche:

Event Driven Programming

Idee: Langsame externe Events asynchron verarbeiten und weitermachen bis das Ergebnis kommt

Ergebnis: Je nach Anwendungsbereich sehr hoher Durchsatz

Event Loop

Anwendungsbereiche:

Event Driven Programming

fs.readFile('config.js',
    // some time passes...
    function(error, buffer) {
        // the result now pops into existence
        http.get(options, function(resp){
            resp.on('data', function(chunk){
                //do something with chunk
            });
        }).on("error", function(e){
            console.log("Got error: " + e.message);
        });
    }
);
⚡ Callback Hell ⚡ Mehr imperativ als deklarativ ⚡

Anwendungsbereiche:

Event Driven Programming

Lösung: Futures/Promises (Continuation Monad)
fs.promisifiedReadFile('config.js')
    .then(fetchSomethingFromWeb)
    .then(processThatData)
    .then(saveItToTheDatabase)
    .catch(function(error) { console.log(error); });
    

Anwendungsbereiche:

Event Driven Programming mit Promises

Viele Implementierungen auch mit, fold, forEach, map etc.
fs.promisifiedReadDir("/home/user/workspace")
    .map(fs.promisifiedReadFile)
    .reduce((total, content) => total += content.length, 0)
    .then(result => console.log(result))
    .catch(error => console.log(error));
    

Agenda

  • Geschichte & Herkunft
  • Sprachgrundlagen & Eigenschaften
  • Funktionale Konzepte
  • Anwendungsbereiche
    • Single-Thread Architektur problematisch für komplexe Berechnungen
    • Event-basierte Programmierung gut mit funktionaler Programmierung kombinierbar
  • Beispiele

Beispiele: Live Coding

  • Sort
  • Currying
  • Funktionen höherer Ordnung

Zusammenfassung

  • Geschichte & Herkunft
  • Sprachgrundlagen & Eigenschaften
  • Funktionale Konzepte
  • Anwendungsbereiche
  • Beispiele

Zusammenfassung

JavaScript ist ...
  • ... einfach zu lernen
  • ... überall zu verwenden
  • ... mit vielen Altlasten und Problemen
  • ... für funktionale Programmierung geeignet
  • ... ist aber nicht für rein funktionale Programmierung entworfen

Quellen

Bücher

Blogs

Sonstige Quellen & interessante Links

let add = function(a, b) {
  return a + b;
} 
let add2 = (a, b) => a + b;

let map = (fn, xs) => {
  if (!xs.length) return [];
  return [fn(xs[0])].concat(map(fn, xs.slice(1)));
};

let inc = a => a + 1;
map(inc, [0, 1, 2]);
let applyFn = (fn, x) => (y) => fn(x, y);
let inc2 = applyFn(add, 1);

let curry = (fn, ...args) => fn.length === args.length
        ? fn(...args)
        : curry.bind(this, fn, ...args);
let inc3 = curry(add)(1);
let sort = xs => {
  if (xs.length === 0) return [];
  let pivot = xs[0], t = xs.slice(1);
  return sort(t.filter(x => x < pivot))
    .concat(pivot)
    .concat(sort(t.filter(x => x >= pivot)));
}
Funktionale Programmierung mit JavaScript λ 1. April: Michael Häuslmann 15. April: Marinus Noichl