OOP ist kalter Kaffee? Let's try FP! map und reduce ohne Big-Data-Kontext

Funktionale Programmierung war mir bislang eher nicht geläufig. OOP ist ja das bekannte Allheilmittel, richtig? Haskell, Scala und Lisp sind ja abgefahrene Sprachen, die nur Wissenschaftler nutzen… Aber ein Vortrag auf DCHH 2013 der  haben mich mir dann doch die Augen geöffnet: Jacob Westhoffs „Pure and functional Javascript“ –   JS ist auch funktional programmierbar!

Eine sehr gute Einführung gibt es hier im ersten und bislang einzigen Javascript Onlinemagazin Mag.JS Funktionales Programmieren: das vernachlässigte Paradigma

Kurze Zusammenfassung der Kerngedanken:

  • FP als Paradigma passt wunderbar zur Ablaufsteuerung
  • FP lässt eleganten Code entstehen, in JS auch durch map und reduce

Einfaches Beispiel

Dieses und die nachfolgenden Beispiele entstammen Jacobs Vortrag und zeigen m.E. gut und deutlich die Mächtigkeit des Paradigmas.

<h1>Funktionale Programmierung: Einfaches Beispiel</h1>
<div id="ausgabe"></div>

<script>

var sessions = [
    { title: 'HTML und so', speaker: 'Hans Musterfrau', description: 'Kleine Einführung in HTML5' },
    { title: 'Javascript und so', speaker: 'Eva Maria Blankofix', description: 'Ausführliche Einfügung in JavaScript' },
    { title: 'PHP und so', speaker: 'Max Musterhauser', description: 'Vorlesung zum Handbuch' }
];    

var extract = function(property) {
    return function(object) {
        return object[property];
    };
};

var wrapIn = function(element) {
    return function(input) {
        return "<" + element + ">" + input + "</" + element + ">";
    };
};

var concatenate = function(accumulation, next ) {
    return accumulation + next;
};

// BISHER
/*
var sessionList = "";
var i, len ;
for (i=0, len = sessions.length; i < len; i ++) {
    sessionList += "<li>" + sessions[i].title + "</li>";
}
*/
// FUNKTIONAL

var sessionList = sessions
    .map( extract("title") )
    .map( wrapIn("li") )
    .reduce( concatenate, " " ) ;

document.getElementById('ausgabe').innerHTML = "<ul>" + sessionList + "</ul>";

</script>

 

und nun noch in PHP

 <h1>Funktionale Programmierung in PHP: Einfaches Beispiel</h1>
<?php
error_reporting(E_ALL);
ini_set('display_errors',1);

$sessions = array(
    array( 'title' => 'HTML und so', 'speaker' => 'Hans Musterfrau', 'description' => 'Kleine Einführung in HTML5' ),
    array( 'title' => 'Javascript und so', 'speaker' => 'Eva Maria Blankofix', 'description' => 'Ausführliche Einfügung in JavaScript' ),
    array( 'title' => 'PHP und so', 'speaker' => 'Max Musterhauser', 'description' => 'Vorlesung zum Handbuch' )
);

function extractItm($property) {
    return function($object) use ($property) {
        return $object[$property];
    };
}

function wrapIn($element) {
    return function($input) use ($element) {
        return "<" . $element . ">" . $input . "</" . $element . ">";
    };
}

function concatenate($accumulation, $next) {
    return $accumulation . $next;
};

// FUNKTIONAL

$sessionExtract = array_map(extractItm("title"), $sessions);
$sessionLi = array_map(wrapIn("li"),  $sessionExtract);
$sessionList = array_reduce($sessionLi, "concatenate", " ");

print "<ul>" . $sessionList . "</ul>";
?>

 

Komplexes Beispiel

<h1>Funktionale Programmierung: Komplexes Beispiel</h1>
<div id="ausgabe"></div>

<script>
var sessions = [
    { title: 'HTML und so', speaker: 'Hans Musterfrau', description: 'Kleine Einführung in HTML5' },
    { title: 'Javascript und so', speaker: 'Eva Maria Blankofix', description: 'Ausführliche Einfügung in JavaScript' },
    { title: 'PHP und so', speaker: 'Max Musterhauser', description: 'Vorlesung zum Handbuch' }
];    

// ----------------------------------------
// Pure Funktionen

var extract = function(property) {
    return function(object) {
        return object[property];
    }
}

var wrapIn = function(element) {
    return function(input) {
        return "<" + element + ">" + input + "</" + element + ">";
    }
}

var concatenate = function(accumulation, next) {
    return accumulation + next;
}

var highlight = function(/* argumente... */) {
    var args = Array.prototype.slice.call(arguments);
    return function (input) {
        args.forEach(function(replacement) {
            input = input.replace (
                new RegExp("\\b" + replacement + "\\b"),
                    "<em>" + replacement + "</em>"
            );
        });
        return input;
    }
}

var ellipsis = function(maxLength) {
    return function(input) {
        if ( input.length <= maxLength ) {
            return input ;
        }
        return input.substring(0, maxLength - 1) + "... ";
    }
}

var prefix = function(prefix) {
    return function(input) {
        return prefix + input ;
    }
}

var uppercaseEveryFirst = function() {
    return function(input) {
        return input
            .split(" ")
            .map( uppercaseFirst() )
            .join(" ") ;
    }
}

var uppercaseFirst = function() {
    return function(input) {
        return input.charAt(0).toUpperCase ( )
            + input.substring(1) ;
    }
}

var join = function(delimiter) {
    return function(input) {
        return input.join(delimiter);
    }
}

var weave = function(a, b, c) {
    var arr = [];
    for (var i=0; i<a.length; i++) {
        arr.push([ a[i], b[i], c[i] ]);
    }
    return arr;
}

// -------------------------------------------------
// Map und Reduce

var titles = sessions
    .map( extract("title") )
    .map( wrapIn("h2") );

var speakers = sessions
    .map( extract("speaker") )
    .map( uppercaseEveryFirst() )
    .map( prefix("Speaker: ") )
    .map( wrapIn("h3") ) ;

var descriptions = sessions
    .map( extract("description") )
    .map( ellipsis(160) )
    .map( highlight("JavaScript", "HTML5") )
    .map( wrapIn("p") ) ;

var result = weave(titles, speakers, descriptions)
    .map( join() )
    .map( wrapIn("div") )
    .reduce( concatenate );

document.getElementById('ausgabe').innerHTML = result;

</script>

Geht auch asyncron!

Mit Hilfe der Async.js Bibliothek funktioniert das ganze auch asynchron:

 <html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="async.js"></script>
<body>
<script>
// Asynchronous map/reduce to the rescue

// https://github.com/caolan/async

var ajax = function ( url , done ) {
    $.ajax({
        url : url ,
        success : done
    });
}

var fetch = function ( urls , done ) {
    async.map( urls , ajax , done );
}

var urls = [
    'http://heise.de/',
    'http://t3n.de/',
    'http://tagesschau.de/'
];

fetch(urls);
</script>

BTW: Async.js hat noch einen ganzen Sack voll Funktionen mehr im Bauch…

 

Fazit

Meine Meinung dazu? Es ist ein Paradigma, was man kennen sollte um es bei passender Gelegenheit einzusetzen.

 

Manuel Strehl schreibt passend dazu:

Unter JavaScript-Programmierern mit OOP-Hintergrund erfreut sich backbone.js als Quasi-MVC-Framework steigender Beliebtheit. Es ist nicht ohne ein bisschen Ironie, dass dessen einzige harte Abhängigkeit das gerade erwähnte underscore.js ist, eine Bibliothek, die fehlende Elemente anderer funktionaler Sprachen in JavaScript umsetzt.

Andererseits zeigt diese Symbiose sehr schön die Flexibilität von JavaScript und die Vielseitigkeit und Mächtigkeit, die sich aus dem Mischen verschiedener Paradigmen ergibt. Wenn man einmal angefangen hat, funktionale Features zu verwenden, wird man Sprachen verfluchen, die Funktionen nicht als Objekte erster Klasse behandeln. Es wird sich anfühlen wie ein Werkzeugkasten, aus dem jemand alle Schraubenzieher gestohlen hat.

In dem Sinne: Happy Coding – habt Spaß daran!