IOException.de

Icon

Ausgewählter Nerdkram von Informatikstudenten der Uni Ulm

Benachrichtigungen für die Piratenpads skripten

Die Piratenpads sind eine bequeme, kostenfreie Möglichkeit um kollaborativ an Texten zu arbeiten oder etwa Ideen zu sammeln. In letzter Zeit nutze ich den Service mit einigen anderen Leuten sehr exzessiv. Über die verschiedenen Pads in verschiedenen Teams habe ich aber inzwischen einfach den Überblick verloren. Um herauszufinden, ob es irgendwo Neuerungen gibt muss ich alles manuell abklappern und mich bei jedem einzelnen Team einloggen. Ich bin der Ansicht, dass man genau solche Aufgaben nach Möglichkeit immer automatisieren sollte.

Deswegen habe ich einen Notificationservice für die Piratenpads geschrieben. Das Skript loggt sich ein, fragt die aktuelle Version des Dokuments ab und speichert diese lokal zwischen. Beim nächsten Überprüfen (idealerweise als Cronjob) wird die lokale Kopie mit dem online-Original verglichen. Gibt es eine Differenz wird eine E-Mailbenachrichtigung mit dem diff versandt.

Um das Ganze zu Skripten habe ich node.js mit der Bibliothek request verwendet:

Request is designed to be the simplest way possible to make HTTP calls. It support HTTPS and follows redirects by default.

Tolle Sache! Ohne HTTPS-Support kommen wir eh nicht weit, bei einigen Pads ist der Zugriff auf HTTPS eingeschränkt. Außerdem muss man sich dank der Bibliothek nicht um das mühsame Parsen von “Set-Cookie” Feldern kümmern. request übernimmt die Cookies standardmäßig einfach global für zukünftige Requests.

Um die Session zu initialisieren, also sich vor dem Login einen Cookie zu holen, sieht der Code etwa so aus:

var request = require('request'); 
var base = 'https://foo.piratenpad.de';

(function initiateSession() {
	request.get(base, function (error, response, body) {
		if (!error && response.statusCode == 200) {
			login();
		}
	});
})();

Die Loginfunktion habe ich zusammengebaut, nachdem ich den kompletten Skriptablauf im Firefox durchgespielt habe und alle Requests mittels des (unheimlich praktischen) Live HTTP Headers Add-ons aufgezeichnet habe.

function login() {
	var options = {
			url: base + '/ep/account/sign-in', 
			form: {'email': 'john@doe.de', 'password' : 'mypw'}
	};
	
	request.post(options, function (err, res, body) {	
			request.get(base + '/' + acc.padId, function (err, resp, body) {
				// get the latest version of the pad document
				var linkToLatestVersion = body.match(/[\w\d\/\-\.]*(latest')/gi);
				linkToLatestVersion = linkToLatestVersion[0].replace("'", '');

				getLatestDocument(linkToLatestVersion);
			});			
		}
	);	
}

Die aktuelle Version des Dokuments lässt sich dann mit einem einfachen GET-Request abfragen:

function getLatestDocument(linkToLatestVersion) {
	request.get(base + linkToLatestVersion, function (err, resp, body) {
		var start = body.search('id="padcontent">') + 'id="padcontent">'.length;
		var end = body.search("<!-- /padeditor -->");
		var padContent = body.substring(start, end);
		
		// strip all tags and entities
		padContent = padContent.replace(/(<[^>]+>)|(&[#\w]+;)/gi, '');
		
		console.log(padContent.trim());
	});
}

Das Ganze zusammengefasst als ordentliches, sauber konfigurierbares, Projekt gibt es hier auf GitHub. Das Skript kann sehr einfach für ähnliche Aufgaben wiederverwendet werden. Als Anregung: Beispielsweise wäre es möglich das Uni Hochschulportal anzuzapfen um E-Mailbenachrichtigungen zu versenden, wenn neue Prüfungsergebnisse eingetragen sind.

Update: Ich habe noch die Möglichkeit hinzugefügt, Benachrichtigungen für Änderungen an dem Inhalt hinter einer URL zu konfigurieren (im Ordner simple-webpage/). Ich benutze das als simple Lösung für Seiten, die keinen RSS-Feed bereitstellen.

Einfache Visualisierung von Geodaten – Teil 2: Leaflet & jquery.couch.js

Im vorherigen Teil haben wir gesehen, wie man Geodaten mithilfe von CouchDB abspeichern kann. Da diese Datenbank zugleich ein Webserver ist und die Daten im JSON-Format gespeichert werden, eignet sie CouchDB auch gut für AJAX-Abfragen. Hierfür gibt es eine auf jQuery aufbauende Library namens jquery.couch.js, die von den AJAX-Requests abstrahiert und direkt browser-seitige Interaktionen mit der Datenbank ermöglicht.

Im diesem Beitrag soll gezeigt werden, wie man mit der offenen Karten-Library Leaflet und jquery.couch.js geographische Daten aus CouchDB heraus auf einer Karten anzeigen kann.

Beispiel-Visualisierung von ulmapi.de, ebenfalls basierend auf CouchDB und Leaflet.

Wir verwenden die CouchApp aus dem ersten Teil weiter, und fügen zu den bisherigen Map/Reduce Views noch statische HTML- und Javascript-Dateien hinzu (im _attachments Ordner), die dann im Browser abgerufen werden können. Beim Aufruf dieser Webseite wird ein HTML-Grundgerüst übertragen, sowie eine JavaScript-Datei, die beim Aufruf die eigentlichen Datensätze via jquery.couch.js aus der CouchDB nachlädt.

Als Mapping-Library verwenden wir Leaflet, eine Open-Source Bibliothek für Kartendarstellungen im Browser. Leaflet abstrahiert von verschiedenen Kartenprovidern und erlaubt es somit, unterschiedliche Datenquellen zu verwenden, wie zum Beispiel auch Bing Maps oder Cloudmade. Letzteres ist ein Service, der auf Basis der Open Street Map Daten Kartenkacheln mit individuellen Stilen rendert und hostet – für Visualisierungen oft sehr hilfreich, da reguläre Karten meist zu viele Karteninformationen enthalten oder farblich überladen sind. In unserem Fall haben wir einen einfach Graustufenkarte gewählt. Leaflet selbst lässt sich relativ leicht verwenden, es muss eine CSS-Datei sowie eine JavaScript-Datei importiert werden, und ein div-Block im HTML enthalten sein, worin später die Karte gerendert werden soll. Somit sieht unser HTML-Gerüst zu Beginn so aus:

<!doctype html>
<html>
<head>
	<link rel="stylesheet" href="style/leaflet.css" />
	<script type="text/javascript" src="js/jquery.min.js"></script>
	<script type="text/javascript" src="js/jquery.couch.js"></script>
	<script type="text/javascript" src="js/leaflet.js"></script>
	<script type="text/javascript" src="js/maploader.js"></script>
</head>
<body>
	<div id="map"></div>
</body>
</html>

Es werden jQuery, jquery.couch.js und die Leaflet-Libs geladen, und die letzte importierte JavaScript-Datei soll nun unseren Code zum initialisieren der Karte und dem Laden der Daten aus der CouchDB enthalten. Zunächst erstellen wir eine Karte und rendern sie, sobald die Seite vollständig geladen wurde (jQuery Callback für document.ready):

$(document).ready(function(){

		var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/[YOUR_API_KEY]/33481/256/{z}/{x}/{y}.png';
		var cloudmadeAttribution = 'UlmApi.de / Shape Files: Stadt Ulm (cc-by-sa), Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade';
		var cloudmade = new L.TileLayer(
			cloudmadeUrl, {
				maxZoom : 18,
				attribution : cloudmadeAttribution
		});

		var map = new L.Map('map', {
			center : new L.LatLng(48.399976,9.995399),
			zoom : 12,
			layers : [ cloudmade ],
			zoomControl : false
		});
});

In der cloudmadeUrl muss für Cloudmade Karte ein korrekter API-Key angegeben werden, der nächste Parameter im Pfad identifiziert den Kartentyp. Beim Initialisieren der Karte wird dann die ID des divs angeben, bei uns ‘map’. Nun sollte unsere Karte bereits dargestellt werden, nachdem wir die CouchApp neu deployen (außerhalb des Fokus dieses Artikels, mehr dazu auf couchapp.org).

Was nun noch fehlt, ist das Nachladen der Geodaten aus der CouchDB und die Anzeige auf der Karte. Hierfür verwenden wir jquery.couch.js als Wrapper für die AJAX-Requests gegen CouchDB und die GeoJSON-Funktionalität von Leaflet:

$.couch.db('database_name').view('design_doc_name/view_name', {

	success: function(data){
		if(data && data.rows && data.rows.length){

			var geoJsonLayer = new L.GeoJSON();

			for(var i = 0;i<data.rows.length;i++){
				geoJsonLayer.addGeoJSON(data.rows[i].value.geometry);
			}

			map.addLayer(geoJsonLayer);
		}
	}
});

Das obige Snippet sollte im vorherigen Code hinter der Erzeugung der Karte eingefügt werden. Es ruft von der Datenbank ‘database_name’ den View ‘view_name’ des Design-Dokuments ‘design_doc_name’ auf, und iteriert bei erfolgreicher Abfrage über alle Zeilen. Von jeder Zeile wird dabei die geometry-Property zu einem GeoJSON-Layer hinzugefügt, der am Ende an die Karte übergeben wird. Da unser View aus Teil 1 bereits GeoJSON generiert, und Leaflet nativ GeoJSON lesen und darstellen kann, ist das Hinzufügen von Geodaten auf die Karte sehr einfach.

Hier noch ein paar weiterführende Links mit vertiefenden Inhalten zu den einzelnen Themen:

Einfache Visualisierung von Geodaten – Teil 1: CouchDB/GeoCouch

Die Hochschulgruppe Open Data Ulm hat es sich zur Aufgabe gemacht, offene und öffentliche Daten rund um die Region Ulm zu aggregieren, aufzuarbeiten und zu visualisieren. Näheres zu diesem Projekt sowie bereits gesammelte Datensätze gibt es unter UlmApi.de

Für unser Vorhaben habe ich als Persistenzlösung die dokumentenorientierte Datenbank CouchDB gewählt, da sie für uns mehrere interessante Features bietet:

  • schemalos: Anders als relationale Datenbanken benötigen schemalose Datenbanken keine im Voraus fest definierte Struktur der Einträge. Für unsere Geodaten ist dies sehr hilfreich, da außer einer ID und den Geodaten noch beliebige zusätzliche Daten pro Eintrag mitgespeichert werden können.
  • JSON: Für die Speicherung strukturierter Daten stellt dieses Format eine leichtgewichtige Alternative zu XML dar.
  • webbasiert: Die Datenbank ist zugleich ein Webserver und der Zugriff auf die Daten läuft somit über HTTP.
  • verteilt/replizierend: Ein wichtiges Konzept von CouchDB ist die einfache aber mächtige Replikation zwischen verschiedenen Instanzen. Im Kontext unserer offenen Datensammlungen ermöglicht dies, dezentralte Kopien der Daten anzulegen, diese lokal zu editieren oder erweitern und wieder auf unsere Hauptdatenbank zu laden.
  • Attachments: Neben strukturierten Daten lassen sich auch ganze Dateien speichern. Dies ist vor allem für archivierte Rohdaten in proprietären Formaten interessant.
  • CouchApps: Neben der Speicherung der Daten sind vor allem einfache Anwendungen interessant, die diese visualisieren oder aufbereiten. Das Konzept der CouchApps ermöglicht es uns, simple Webanwendungen direkt auf der Datenbank zu deployen und verfügbar zu machen.
  • räumliche Indizes: Dank Volker Mische besitzt CouchDB einen zusätzlichen Index (GeoCouch), der statt eindimensionaler B-Bäume zweidimensionale R-Bäume benutzt. Damit lassen sich Dokumente mit räumlichen Daten abspeichern, indizieren und effizient abfragen.

Von der Stadt Ulm haben wir als ersten Datensatz Shapefiles der Ulmer Stadtteile und Stadtviertel bekommen. Diese wurden zunächst vom Ausgangsformat (Gauss-Krueger-Shapefiles) in das GeoJSON-Format mit WGS84-Koordinaten konvertiert. Mithilfe eines kleinen node.js Skriptes wurden die einzelnen Shapes dann als Dokumente auf die Couch geladen.

Ein Dokument hat hierbei folgende Form (Originaldokument):

{
   "_id": "ul-st14",
   "_rev": "1-797187e292d93b6d661ca8f7fec3f6c9",
   "type": "stadtteil",
   "name": "Weststadt"
   "geometry": {
       "type": "Feature",
       "properties": {
           "identifier": "ST 14",
           "name": "Weststadt"
       },
       "geometry": {
           "type": "Polygon",
           "coordinates": [
               [
                   [
                       9.981459,
                       48.395751
                   ],
                   …
              ]
           ]
       }
   },
   "creator": "Stadt Ulm",
   "license": {
       "name": "Creative Commons - Namensnennung-Weitergabe unter gleichen Bedingungen 3.0 Deutschland (CC BY-SA 3.0)",
       "link": "http://creativecommons.org/licenses/by-sa/3.0/de/"
   },
}

Die _id bestimmt das Dokument eindeutig, das _rev Feld ist für die Versionskontrolle verantwortlich. Als ID haben wir einen internen Identifier der Stadt genommen und noch mit einem “ul” Präfix versehen. Der Rest des Dokuments kann frei strukturiert werden. In unserem Fall verwenden wir noch ein type Feld, durch das wir später Dokumente unterschiedlichen Typs unterscheiden können (z.B. stadtteil oder stadtviertel). Das geometry Feld (hier gekürzt) enthält die geografischen Daten im GeoJSON-Format. Die sonstigen Felder beschreiben noch den Urheber und die Lizenz der Daten sowie den Namen des Eintrags.

Nun ist die Datenbank gefüllt, später sollen aber die Daten auch wieder abgefragt werden können. Als “NoSQL” Datenbank bietet CouchDB hierfür aber keine SQL-Statements an, stattdessen muss man mithilfe von MapReduce beschreiben, wie aus den Daten Indizes gebildet werden sollen:

function(doc) {
	if (doc.type) {
		if(doc.type === 'stadtviertel'){
		    emit(['stadtviertel', doc._id], {
		    	'geometry' : doc.geometry,
		    	'label' : "<b>Stadtviertel "+doc.name+"</b><br/>ID: "+doc._id+"<br/>(Stadteil "+doc.stadtteil+")"
		    });
		}
		else if(doc.type === 'stadtteil'){
		    emit(['stadtteil', doc._id], {
		    	'geometry' : doc.geometry,
		    	'label' : "<b>Stadtteil "+doc.name+"</b><br/>ID: "+doc._id
		    });
		}
	}
};

Damit erzeugen wir eine sortiere Liste von Schlüssel-Wert-Paaren. Der Schlüssel ist selbst wieder komplex und besteht aus zwei Teilen. Der erste Teil ist der Typ, der zweite Teil die ID des Dokuments. Damit kann man später durch die sogenannte View Collation Abfragen durchführen, die sich auf einen bestimmtem Typ beschränken (zur Wiederholung: ohne SQL gibt es hier auch kein WHERE Statement). In diesem Fall werden bisher nur Dokumente des Typs stadtteil oder stadtviertel eingetragen, und als Wert eines Eintrages wird bereits die spätere Nutzung auf einer Karte vorbereitet – es werden die Geodaten sowie ein Label indiziert. Damit lassen sich nun schon Stadtteile/Stadtviertel abfragen.

Ergänzt man dies noch um einen räumlichen Index, so werden auch räumliche Abfragen ermöglicht. Hierfür werden in den Index als Schlüssel die Geodaten (unverändert im GeoJSON Format) eingetragen, den Wert selbst bliebt leer, da das Feld _id sowieso eingetragen wird und vorest keine weiteren Daten mehr benötigt werden:

function(doc){
	if(doc.geometry && doc.geometry.geometry){
		emit(doc.geometry.geometry, null);
	}
};

(Das etwas merkwürdig anmutende doc.geometry.geometry entstand einerseits dadruch, dass unser Feld mit dem GeoJSON-Objekt geometry heißt, das GeoJSON-Objekt selbst aber komplex ist und nur in einem Teil davon die eigentlichen Geodaten hinterlegt sind.)

Mithilfe dieses Index lässt sich nun bei einem gegebenen geografischen Raum überprüfen, welche Objekte darin enthalten sind. Also zum Beispiel ausgehend von einer Koordinate, ob sie sich in Ulm befindet und wenn ja, in welchem Stadtteil/Stadtviertel.

Im nächsten Teil wird näher betrachtet, wie die nun abgespeicherten und indizierten Geodaten im Browser auf einer Karte dargestellt werden können, und zwar direkt aus CouchDB heraus.

Präsentieren mit HTML5-Foliensätzen

Kurzversion: HTML5 Foliensatz, basierend auf einem Google Template, das zusätzlich Notizen und einen Presenter Mode bietet: Demo / Code

Nicht nur die Frage, wie man richtig präsentiert (Stichwort Zen vs. Death by PowerPoint), sondern auch die Frage, mit welchen Anwendungen man präsentiert, ist oft umstritten. Ich persönlich konnte mich bisher mit Powerpoint und Konsorten eher wenig anfreunden – vor allem Typografie und Einschränkungen bei der Gestaltung waren problematisch. Als Alternative habe ich bisher oft LaTeX Beamer verwendet, was allerdings je nach visueller Komplexität auch oft relativ zeitaufwendig ist, sich aber zumindest bei Grafiken in Vektorformaten und mathematischen Inhalten auch schnell auszahlt.

HTML5-basierte Foliensätze

Mit dem Aufkommen von HTML5 entstand eine zusätzliche Möglichkeit. Dank der neuen Multimedia-Tags wie <audio> und <video> sowie mächtigeren CSS Stilen bietet HTML nun die Grundlagen für browser-basierte Präsentationen. Mittlerweile gibt es hierfür auch schon mehrere Templates:

Noch mehr Alternativen gibt es in dieser Auflistung. Ein weiteres sehr schönes Beispiel ist ein Foliensatz zu HTML5, der selbst quasi eine Technologiedemonstration enthält: http://slides.html5rocks.com

Der Vorteil von HTML-basierten Präsentationen ist die hohe Anzahl von Medien (u.a. auch SVG, Flash Videos oder ganze Webseiten als IFrames), die man einbetten kann. Ein einfaches, weitläufig bekanntes Markup ermöglicht das schnelle Erstellen von Folien, und mit einer Kombination aus HTML, CSS und JavaScript lassen sich dennoch auch komplexe Spezialfunktionen realisieren.

Mir persönlich hat das html5slides Template ganz gut gefallen, das Google entwickelt und für die Google I/O Slides eingesetzt hat. Da das Template unter einer Apache License veröffentlicht wurde, habe ich zunächst damit begonnen, es an das Uni Ulm Corporate Design anzupassen. Außerdem hatte ich ein paar kleine Änderungen am Code vorgenommen, um zum Beispiel eine Nummerierung der Folien zu ermöglichen.

Presenter Mode?

Prinzipiell war das Ergebnis schon mal akzeptabel, allerdings wurden die oft genannten Probleme bei solchen HTML-Foliensätzen schnell offensichtlich – fehlende Notizen für den Vortragenden und nur eine Ausgabe.

Eher durch Zufall bin ich auf ein anderes Konstrukt gestoßen, dass seit HTML5 Cross-Frame-Communication erlaubt, also den Austausch von Nachrichten zwischen zwei verschiedenen Frames (mit einigen Einschränkungen): window.postMessage()

Die Möglichkeit, zwischen Frames zu kommunizieren, ist natürlich auch ideal dafür, Daten zwischen Frames zu synchronisieren. Übertragen auf zwei verschiedene Präsenationsframes ermöglicht dies beim Weiterschalten der Folien in einem Frame, den zweiten Frame zu aktualisieren. Schematisch sieht das so aus:

(CC-BY-NC) Icons by picol.org / w3.org

Im Hauptfenster kann per Tastendruck ein zusätzliches Popup geöffnet werden (1). Das neue Popup öffnet die gleiche URI der Präsentation und wird auf dem Bildschirm des Vortragenden platziert. Schaltet der Vortragende nun im Hauptfenster weiter zu nächsten Folie, so erzeugt dies ein Nachricht an das zweite Frame (2), das dann ebenfalls weiterschaltet.

Eine weitere Ergänzung war die Unterstützung von Notizen als Overlay über die Folien. Kombiniert mit dem Dual-Screen-Ansatz ermöglicht dies, dem Publikum die Folien zu zeigen, dem Vortragenden auf einem zweiten Bildschirm die Folien plus den verfügbaren Notizen.

Ausführliche Beispiele mit Code gibt es in einer Beispielpräsentation, den kompletten Code auf github: https://github.com/berb/html5slides-uulm

Auf der Feature Wishlist steht noch ein alternativer CSS-Stylesheet für den Druckexport. Außerdem ein Tool, dass externe Daten wie Bilder per Base64 encoding als Data URI integriert und JavaScript sowie Stylesheets inline einbindet, sodass die Präsentation als einzelne HTML5 Datei ohne externe Abhängigkeiten abgespeichert werden kann.

Funktionale Ansätze in einer imperativen Programmiersprache

Im Zuge der Vorlesung “Paradigmen der Programmierung” kam ich erstmals mit dem alternativen Programmierkonzept der funktionalen Programmierung in Berührung, das sich von dem der weiter verbreiteteren imperativen Programmiersprachen wie etwa Java und C grundsätzlich unterscheidet. Als Übung auf die Klausur versuchte ich die Ideen der funktionalen Programmiersprache Haskell in einer imperativen Sprache umzusetzen. In Javascript ist die Nutzung von anonymen Funktionen und ihre Übergabe selbst als Parameter einer anderen Funktion bereits verbreitet, womit sie per se bereits einige Konzepte der funktionalen Programmierung umsetzt. Um weitere Dinge wie partielle Funktionsaufrufe ebenfalls zu ermöglichen, habe ich ein Javascript-Modul “functionalJS” geschrieben, das mit dem Konstruktor Functional() ein Äquivalent zum Javascript-eigenen Function() bietet. Der Quellcode sowie einige Beispiele finden sich auf github. Im Folgenden möchte ich die Ideen der funktionalen Programmierung kurz vorstellen und erklären, wie sie in dieser Klasse umgesetzt wurden.

Alles ist eine Funktion

Beim funktionalen Programmierstil besteht das Programm einzig aus Funktionen. Auf den ersten Blick mag das bei imperativen Programmiersprachen kaum anders sein, auch hier bilden Funktionen bzw. Methoden einen wesentlichen Bestandteil. Es gibt jedoch Unterschiede bei der Art der Aufrufe: Imperative Programme führen die Funktionen als eine Folge von Anweisungen aus (“Tu das! Dann tu das solange dies gilt!” – imperativ eben), bei der funktionalen Programmierung treten die Funktionen nur ineinander geschachtelt auf – das Ergebnis einer Funktion ist also zugleich Parameter der nächsten. Dadurch sind Variablen unnötigt, was einen Vorteil der funktionalen Programmiersprachen begründet: Es gibt keine Seiteneffekte! Während in der imperativen Programmierung das Resultat vom Programmzustand abhängt, hängen hier die Rückgabewerte einzig von den Parametern ab und etwa nicht vom Zeitpunkt des Aufrufes.

Für die Umsetzung in Javascript bedeutet dies, dass alle Funktionen ohne lokale Variablen auskommen, sondern stattdessen einzig ein return-Statement beinhalten. In diesem können nach dem eben genannten Konzept Funktionen aufgerufen werden. Dadurch ist Rekursion möglich, die nötig wird, um Schleifen zu vermeiden. Jede Funktion muss zudem mindestens einen Parameter und genau einen Rückgabewert besitzen, um dem funktionalen Anspruch gerecht zu werden.

Beispiel: Durchschnittsfunktion für eine übergebene Liste in Javascript (Ausschnitt der functional.js)

function(l) { return ratio(sum(l), length(l)); }

Mustervergleiche (Stichwort: Pattern matching)

Haskell erlaubt die mehrfache Definition einer Funktion. Dies unterscheidet sich wesentlich vom “Überladen” bei Java, wo die Unterschiede bei der mehrfachen Definition in der Methodensignatur liegen. In funktionalen Programmiersprachen werden dagegen semantische statt syntaktische Muster für die Eingabeparameter angelegt, d.h. es wird quasi eine Musterbasis aufgebaut, die dann beim Aufruf der Funktion von oben nach unten getestet wird. Sobald das erste Muster mit den übergebenen Parametern zusammenpasst, wird dessen Ergebnis oder Funktionsrumpf genutzt. Das Ergebnis hängt also wesentlich von der Reihenfolge der Musterdefinitionen ab. Basisfälle für Rekursionen werden so stets als erstes definiert, der allgemeinste Fall zum Schluss.

Beispiel: (Primitive) Definition der Fibonacci-Funktion in Haskell:

fibonacci 0 = 0
fibonacci 1 = 1
fibonacci n = fibonacci (n-1) + fibonacci (n-2)

Die beiden Basisfälle der Fibonacci-Funktion werden also vor der allgemeinen Rekursionsvorschrift definiert. In Javascript ist ein Überladen von Funktionen nicht möglich, auch bei der functionalJS-Klasse müssen so die Muster gleich dem Konstuktor übergeben werden. Eine äquivalente Definition der Fibonacci-Funktion mittels der functionalJS-Klasse sieht so aus:

Beispiel: Definition der Fibonacci-Funktion in Javascript (modifizierter Ausschnitt der test.js)

fibonacci = new Functional("fibonacci",
	[0, 0],
	[1, 1],
	["n", function(n) { return fibonacci(n-1)+fibonacci(n-2); }]
);

Die Muster werden als Arrays dem Functional übergeben. Dabei sind für reine Funktionen wie der letzte Fall auch Verkürzungen möglich. Näheres dazu ist in der Dokumentation aufgeführt. Ansonsten gilt: Das letzte Array-Element ist das Resultat oder die aufzurufende Funktion bei Übergabe dieses Musters. Optional kann als einfacher String der Name der Funktion übergeben werden, was für die interne Realisierung der Rekursion nötig ist.

Wird nun die Fibonacci-Funktion aufgerufen, so werden die Muster mittels Pattern matching nacheinander mit den tatsächlichen Parametern verglichen. Dabei sind durchaus auch kompliziertere Muster möglich, die die Haskell-typischen Listen erkennen. So wird die einfache length-Funktion, die die Anzahl der Elemente einer Liste zurückgibt, in beiden Sprachen wie folgt definiert:

Beispiel: Definition der length-Funktion in Haskell

length []	=  0
length (x:xs)	=  1 + length xs

Analog sieht eine solche Definition mittels functionalJS aus, mit dem Unterschied, dass hier für Listen (egal ob leer oder nicht) stets die eckigen Klammern genutzt werden:

Beispiel: Definition der length-Funktion in Javascript (Ausschnitt der functional.js)

length = new Functional("length",
	["[]", 		0],
	["[x:xs]", 	function(x, xs) { return 1+length(xs); }]
);

Über vordefinierte special operators ist es auch nach der Definition eines Functionals möglich, die Musterbasis zu erweitern. Dies könnte in diesem Beispiel etwa wie folgt geschehen, wodurch die Funktion bei Übergabe einer leeren Liste 42 zurückgibt:

Beispiel: Nachträgliche Erweiterung der Musterbasis in Javascript

length('__addOnTop',
	["[]", 42]
);

In Haskell ist ein solches Verhalten nachträglich etwa im GHCi, dem Haskell-Interpreter, nicht möglich. Dort könnten weitere Muster stets nur am Ende das Basis hinzugefügt werden. Ein solches Verhalten lässt sich bei functionalJS mittels des Keywords ‘__add’ realisieren.

Partielle Aufrufe (Stichwort: Currying)

Haskell erlaubt das partielle Aufrufen von definierten Funktionen, d.h. das Übergeben zu weniger Parameter um daraus eine neue Funktion zu erhalten. Dieses Verhalten ist sinnvoll, um eigene und vordefinierte Funktionen mehrfach nutzen zu können und insbesondere in der Verwendung als Funktional (siehe nächsten Punkt). Anders als bei imperativen Programmiersprachen stellen Übergabeparameter einer Funktion also kein Tupel dar (param1, param2, param3), sondern lassen sich eher als Liste param1 -> param2 -> param3 -> result auffassen. Werden alle Parameter übergeben, so wird ganz normal das Funktionsresultat zurückgegeben. Wird die Funktion dagegen nur mit zwei Parametern aufgerufen, so wird ein neue Funktion zurückgegeben, die von einem Parameter abhängt. Man sagt, dass eine Funktion mit einer Stelligkeit von 3 bei Übergabe von zwei Parametern eine Funktion der Stelligkeit 1 zurückliefert.

Das Verhalten, dass die Anzahl der Parameter einer Funktion in Javascript variabel sein kann, kann durch die Nutzung des arguments-Objekts realisiert werden. Schwieriger gestaltet sich die Realisierung der partiellen Auswertung (Ausschnitt). Während bei der normalen Auswertung, also bei Übergabe aller Parameter, einfach das erste Muster gesucht wird, welches zutrifft, müssen bei der partiellen Auswertung alle zutreffenden Muster gesucht werden, da diese in dieser Reihenfolge Teil des zurückzugebenen Functionals werden. Anhand dieser Muster wird ein neues Functional erzeugt, in deren Funktionsrümpfe die gebundenen Variablen durch die gematchten Parameter ersetzt werden. So wird also aus der zweistelligen add-Funktion (Definition), die die Summe zweier Zahlen berechnet, bei Übergabe des Parameters 1 ein einstelliges Functional, das eine Zahl erwartet und als Resultat den Nachfolger der Zahl zurückliefert.

Beispiel: Definition der Nachfolger-Funktion durch partiellen Aufruf in Javascript

succ = add(1); // add :: a -> a -> a

Anders als in Haskell können Funktionsnamen in functionalJS nur aus Buchstaben und Zahlen bestehen, eine Funktionsdefinition als “<” ist so etwa nicht möglich, stattdessen gibt es eine lessthan-Funktion. Dies erschwert partielle Aufrufe von nicht-kommutativen Funktionen.

Beispiel: Falsche Definition der negative-Funktion durch partiellen Aufruf in Javascript

negative = lessthan(0);

Die oben genannte Definition der negative-Funktion liefert eben nicht true zurück für Zahlen kleiner null, sondern genau das Gegenteil. Dies liegt an der Definition von lessthan:

Beispiel: Definition der standardmäßigen lessthan-Funktion (Ausschnitt der functional.js)

lessthan = new Functional("lessthan",
	function(a, b) { return a < b; }
);

Der partielle Aufruf lessthan(0) liefert also ein neues einstelliges Functional zurück, dass stets 0<x prüft. Dies entspricht also nicht dem gewünschten Verhalten. In Haskell sind dagegen auch partielle Aufrufe wie oben gewünscht möglich:

Beispiel: Definition der negative-Funktion in Haskell

negative = (<0)

Daher sind in den standardmäßigen Funktionen der functionalJS-Klasse zu jeder zweistelligen nicht-kommutativen Funktion auch eine entsprechende mit verdrehten Parametern definiert, die den gleichen Namen endend auf “Re” trägt. Unser obiges Beispiel müsste also korrekt lauten:

Beispiel: Korrekte Definition der negative-Funktion durch partiellen Aufruf in Javascript

negative = lessthanRe(0);

Funktionen als Parameter (Stichwort: Funktionale)

Die Möglichkeit des partiellen Aufrufs einer Funktion ist bereits ein mächtiges Instrument. Seine volle Wirkung können solche Funktionen aber bei Übergabe an andere entfalten. Anders als in vielen anderen imperativen Programmiersprachen ist die Übergabe von Funktionen als Parameter in Javascript möglich und ein häufiger Einsatzzweck, insbesondere als Callbacks für Events. Javascript-Anhängern ist ein solches Verhalten also bereits bekannt. In der funktionalen Programmierung bezeichnen wir Funktionen, die als Eingabe oder Ausgabe selbst Funktionen aufnehmen, als Funktionen höherer Ordnung oder Funktionale. So ist es etwa bei der Map-Funktion:

Beispiel: Definition der map-Funktion in Javascript (Ausschnitt der functional.js)

map = new Functional("map",
	["f", 	"[]", 		function(f) { return []; }],
	["f", 	"[x:xs]", 	function(f, x, xs) { return unshift(f(x), map(f, xs)); }]
);

Wie wir sehen, erwartet die map-Funktion also zwei Parameter: Zum einen eine Funktion f, zum anderen eine Liste. Das erste Muster beschreibt den Basisfall der leeren Liste, während der zweite den Rekursionsfall nennt. Kurz gesagt: Im Falle der leeren Liste wird eine leere Liste zurückgegeben, ansonsten wird das Kopfelement genommen, die Funktion f darauf angewendet, und das ganze mit dem Rest der Liste wiederholt und zusammengefügt. Die map-Funktion führt also eine Funktion f auf alle Elemente einer Liste aus. Bekannte Haskell-Beispiele der Funktionale sind auch die fold-Funktionen, die ebenfalls in dem Modul definiert wurden.

Unter Anwendung der Funktionen höherer Ordnung sowie partieller Aufrufe lassen sich so sehr schnell nette Funktionen zusammenstellen:

Beispiel: Anwendungen (Ausschnitt der test.js)

foldr(mult, 1, [1,2,3,4]); // 24, berechnet das Produkt aller Listenelemente

all(lessthanRe(5), [1,2,3,4]); // true, da alle Elemente kleiner 5

until(greaterthanRe(80), mult(2), 1); /* 128, da der Initialwert 1 solange mit 2 multiplitziert wird, bis der Schwellenwert 80 erreicht ist */

Fazit

Den funktionalen und imperativen Programmierstil zu vereinen ist schwierig und mag in der Frage münden: Warum? Zum einen beschneidet man die mächtigen Instrumente von Javascript, indem etwa Variablen und Schleifen komplett verboten werden, zum anderen kann man nicht aus den vollen Vorteilen von Haskell schöpfen, etwa bei der Nutzung unendlicher Listen. Und dennoch bieten die Functionals einige Vorteile, allen voran die partiellen Aufrufe. Ob durch die strikte Nutzung des funktionalen Stils tatsächlich auch in Javascript Seiteneffekte ausbleiben, wäre noch zu testen. Insbesondere, da sich einige Funktionen derzeit nicht atomar realisieren lassen (es gibt etwa keinen “(x:xs)”-Operator zur Listenerzeugung), darf in der aktuellen Fassung nicht davon ausgegangen werden. Prinzipien wie anonyme Funktionen und Funktionale sind in Javascript bereits enthalten, wodurch es sich zum Teil bereits ähnlich anfühlt. Das oben geführte until-Beispiel würde man mit normalen Javascript-Mitteln vermutlich wie folgt lösen:

Beispiel: Aufruf einer herkömmlichen until-Funktion in Javascript

until(function condition(x) { return x > 80; }, function do(x) { return x*2; }, 1);

Während ein solches Verhalten nicht in allen imperativen Programmiersprachen möglich ist, bietet Javascript offensichtlich bereits etwas ähnliches an. Auf partielle Aufrufe und die einfache Funktionsdefinition mittels Pattern matching muss man dennoch verzichten.

In der vorgestellten Klasse fehlen noch Haskell-typische Elemente, allen voran die Implementierung von Tupeln. So ist derzeit die bekannte zip-Funktion noch nicht eingebaut. Daneben ist die Implementierung der Mengendefinition, also das einfache Erzeugen einer Liste anhand einer Bildungsvorschrift, umsetzbar. Komplizierter wird es bei der Realisierung von unendlichen Listen, wo Haskell etwa in der Berechnung großer Fibonacci-Zahlen imperativen Programmiersprachen überlegen ist. Schlussendlich lassen sich dann auch ganz grundsätzliche Dinge, wie etwa die Nachbildung der lazy-evaluation als Auswertungsstrategie nicht nachträglich in Javascript umsetzen.

TCP Echo-Server in einer Zeile node.js

Der folgende Einzeiler erzeugt einen TCP-Server, der alle eingehenden Daten direkt zurücksendet:

require('net').createServer(function(s){require('util').pump(s,s);}).listen(8000);

Und nein, das ist kein Perl ;-)

GoogleMap Vaadin Widget (Google Maps JavaScript API V3)

Wer gerade dabei ist, eine Anwendung mit dem Web Application Framework Vaadin zu realisieren und darüber hinaus beabsichtigt, Google Maps in sein User Interface zu integrieren, hat mehrere Möglichkeiten.

Am naheliegendsten ist es zweifellos, einfach das fertige Vaadin Add-on GoogleMapWidget von Henri Muurimaa dafür zu nutzen, welches die Google Maps JavaScript API V2 einsetzt. Wenn man nun allerdings nicht nur die grundlegenden Funktionalitäten benötigt, kommt die Frage auf, ob man Anpassungen an diesem Widget vornehmen oder ein komplett neues Widget implementieren will.

Für den Fall, dass man sich dazu entschieden hat, ein eigenes Widget zu entwickeln, wäre es natürlich unsinnig, dafür noch die alte API Version und nicht die aktuelle Version, nämlich die Google Maps JavaScript API V3, zu verwenden.

Projektstruktur des Beispielprojekts IOException

Die meisten Entwickler möchten an dieser Stelle jedoch vermutlich kein JavaScript benutzen, da es ja einer der großen Vorteile von Vaadin ist, dass man die gesamte Anwendung in Java implementieren kann.

Also begibt man sich auf die Suche nach einer passenden GWT Library, welche Java Wrapper für die JavaScript API bereitstellt. Auf einer der offiziellen Seiten über die GWT APIs wird man dann aber schnell durch folgende Aussage enttäuscht: “At the present time, the gwt-maps API only supports Google Maps API version 2.

Jetzt hat man zwei Möglichkeiten: Entweder man wartet bis Google offiziell eine Library für die Version 3 zur Verfügung stellt, wozu es allerdings keine genauen Angaben gibt, wann das geschehen soll, oder aber man verwendet einfach die Alpha-Version der besagten GWT Library, die auf einer inoffiziellen Seite von Google zu finden ist.

Wer genug Zeit zum Warten und sich aus diesem Grund für die erste Möglichkeit ent- schlossen hat, braucht nun eigentlich nicht mehr weiterzulesen ;-) Allen Anderen soll nachstehend am Beispielprojekt IOException gezeigt werden, wie man das Grundgerüst für sein eigenes GoogleMap Vaadin Widget mit Hilfe der GWT Library gwt-google-maps-v3 (Alpha-Version) erstellt.

Anmerkung: Im Folgenden werden lediglich die essenziellen Schritte aufgeführt. Für die Grundlagen zur Erstellung eines Vaadin Projekts wird auf das Book of Vaadin verwiesen, das vor allem für Anfänger sehr empfehlenswert ist.

Zuerst erzeugt man ein gewöhnliches Vaadin Projekt und legt daraufhin die im Screenshot dargestellte Projektstruktur an. Die JAR-Datei gwt-maps3-0.2b.jar (GWT Library) kann in der Rubrik Downloads der bereits erwähnten inoffiziellen Seite von Google heruntergeladen werden.

MANIFEST.MF


Manifest-Version: 1.0
Vaadin-Widgetsets: de.ioexception.widgets.MainWidgetset.gwt.xml

In der Manifest-Datei muss durch das Attribut Vaadin-Widgetsets angegeben werden, wo sich der oberste GWT Modul Descriptor befindet, welcher das zentrale Widget Set definiert und auch als Einstiegspunkt zum Kompilieren dient.

web.xml (Ausschnitt)


<init-param>
    <param-name>widgetset</param-name>
    <param-value>de.ioexception.widgets.MainWidgetset</param-value>
</init-param>

Dies muss zusätzlich im Deployment Descriptor als <init-param> innerhalb der <servlet>-Tags aufgeführt werden.

MainWidgetset.gwt.xml (Ausschnitt)


<module>
    <inherits name="com.vaadin.terminal.gwt.DefaultWidgetSet" />
    <inherits name="de.ioexception.widgets.googlemap.GoogleMapWidgetset" />
</module>

Wie bereits erwähnt, wird in der Datei MainWidgetset.gwt.xml das zentrale Widget Set definiert. Dazu wird erst einmal vom Default Widget Set und anschließend von den GWT Modul Descriptoren der einzelnen Widget Sets geerbt. Da
das gezeigte Beispielprojekt jedoch nur das GoogleMap Widget beinhaltet, ist dies hier auch die einzige Angabe.

GoogleMapWidgetset.gwt.xml (Ausschnitt)


<module>
    <inherits name="com.vaadin.terminal.gwt.DefaultWidgetSet" />
    <inherits name="com.google.gwt.maps.Maps" />
    <script src="http://maps.google.com/maps/api/js?sensor=false" />
    <source path="client" />
</module>

Um die JAR-Datei gwt-maps3-0.2b.jar (GWT Library) einzubeziehen, muss von deren GWT Modul Descriptor geerbt werden. Dies geschieht durch <inherits name="com.google.gwt.maps.Maps" />. Durch den <script>-Tag erhält man Zugriff auf die Google Maps JavaScript API. Anschließend wird noch der Pfad, an dem sich die clientseitigen Dateien befinden, definiert.

GoogleMap.java


package de.ioexception.widgets.googlemap.server;

import java.util.Map;
import com.vaadin.terminal.PaintException;
import com.vaadin.terminal.PaintTarget;
import com.vaadin.ui.AbstractComponent;
import com.vaadin.ui.ClientWidget;
import de.ioexception.widgets.googlemap.client.VGoogleMap;

@ClientWidget(VGoogleMap.class)
public class GoogleMap extends AbstractComponent
{
    @Override
    public void paintContent(PaintTarget target) throws PaintException
    {
        super.paintContent(target);
    }

    @Override
    public void changeVariables(Object source, Map<String, Object> variables)
    {
        super.changeVariables(source, variables);
    }
}

Die Klasse GoogleMap.java ist an dieser Stelle nur der Vollständigkeit halber aufgeführt und bedarf eigentlich keiner weiteren Erklärung, da dies der gewöhnliche Aufbau einer serverseitigen Widget-Klasse ist und erst einmal nicht erweitert werden muss.

VGoogleMap.java


package de.ioexception.widgets.googlemap.client;

import com.google.gwt.maps.client.base.LatLng;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.MapTypeId;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
import com.vaadin.terminal.gwt.client.ApplicationConnection;
import com.vaadin.terminal.gwt.client.Paintable;
import com.vaadin.terminal.gwt.client.UIDL;

public class VGoogleMap extends Composite implements Paintable
{
    private SimplePanel wrapperPanel = null;
    private MapWidget mapWidget = null;
    private MapOptions mapOptions = null;

    public VGoogleMap()
    {
        wrapperPanel = new SimplePanel();

        initWidget(wrapperPanel);
    }

    @Override
    public void updateFromUIDL(UIDL uidl, ApplicationConnection client)
    {
        if(mapWidget == null)
        {
            initMap();
        }
    }

    private void initMap()
    {
        mapOptions = new MapOptions();

        mapOptions.setZoom(17);
        mapOptions.setCenter(new LatLng(48.42285529218286, 9.957287907600403));
        mapOptions.setMapTypeId(new MapTypeId().getSatellite());
        mapOptions.setScrollwheel(true);
        mapOptions.setDraggable(true);
        mapOptions.setNavigationControl(true);
        mapOptions.setMapTypeControl(true);

        mapWidget = new MapWidget(mapOptions);

        mapWidget.setWidth("800px");
        mapWidget.setHeight("500px");

        wrapperPanel.add(mapWidget);
    }
}

In der clientseitigen Widget-Klasse ist zu sehen, dass ein MapWidget nur mit MapOptions als Parameter initialisiert werden kann. Verpflichtend sind Werte zu den Eigenschaften center, mapTypeId und zoom. Im Absatz MapOptions der Google Maps JavaScript API V3 kann dies nachgelesen werden.

IOException.java


package de.ioexception;

import com.vaadin.Application;
import com.vaadin.ui.Window;
import de.ioexception.widgets.googlemap.server.GoogleMap;

public class IOException extends Application
{
    @Override
    public void init()
    {
        Window mainWindow = new Window("IOException");
        GoogleMap googleMap = new GoogleMap();
        mainWindow.addComponent(googleMap);
        setMainWindow(mainWindow);
    }
}

Wie hier zu sehen ist, kann das GoogleMap Widget nun völlig unkompliziert verwendet werden. Als Beispiel dient dafür die Anwendungsklasse IOException, in der das initialisierte Widget einfach zum Main Window hinzugefügt wird.

Effiziente Bereichs-Queries mit CouchDB/GeoCouch

Im Vergleich zu klassischen SQL-Datenbank erfordern NoSQL-Datenbank vor allem bei der Datenabfrage ein Umdenken. Im Falle von CouchDB lässt sich zwar mit View Collation schon einiges erreichen, allerdings bei weitem nicht alles. Auf eine solche Grenze bin ich gestoßen, als ich Zeiträume speichern und abfragen wollte, also Einträge die ein Start- und Enddatum besitzen. Anfragen auf diese Daten könnten nun zu einem fixen Zeitpunkt alle darin ablaufenden Einträge erfragen, oder ausgeweitet auf einen Zeitraum auflisten, welche Einträge innerhalb eines Zeitfensters liegen. All dies ist mit CouchDB nicht wirklich lösbar.

Einen kleinen Workaround bietet die Idee, die Zeitleiste zu segmentieren und immer dann für einen Eintrag einen Key zu emitten, wenn der Zeitraum des Eintrages innerhalb dieses Bereichs liegt. Eine solche Map-Funktion könnte wie folgt aussehen. Hierbei wird für einen Eintrag jeweils ab dem Beginn für alle 5 Minuten ein Schlüssel in den Index emittiert.

Dokumentaufbau:

{
   "_id": "s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de",
   "begin": "2010-08-05T09:11:52.156Z",
   "end": "2010-08-05T09:23:13.457Z"
}

Map-Funktion:

//length of time segment (here 5 min)
var periodLength = (60*5);

function(doc) 
{
        if(doc.begin && doc.end)        
        {
                //start and end time as UNIX timestamps (seconds, not milliseconds)
                var begin =  Math.round(new Date(doc.begin).getTime()/1000);
                var end =  Math.round(new Date(doc.end).getTime()/1000);

                //calculate first matching segment of period
                var p = (begin - (begin%periodLength));

                //emit key for each matching period
                while(p<=end)
                {
                        emit([p, doc._id], null);
                        p = p + periodLength;
                }
        }
}

Der resultierende View sieht dann in etwa so aus:

Oder als Abfrage:
http://localhost:5984/entries/_design/entries/_view/docsByPeriodList?startkey=[1280998800]&endkey=[1281000193,{}]

{"total_rows":3,"update_seq":2,"offset":0,"rows":[
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1280999400,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"]15,"value":null},
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1280999700,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"],"value":null},
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","key":[1281000000,"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de"],"value":null}
]}

Eine viel bessere Lösung bietet jedoch die CouchDB-Erweiterung GeoCouch. Dank des R-Trees lassen sich Bereichtsabfragen effizient durchführen. Da GeoCouch eigentlich für zweidimensionale Geokoordinaten gedacht, muss sie und die GeoJSON-Syntax etwas missbraucht werden. Anstatt einer WGS84-Koordinate emittieren wir einfach einen UNIX-Zeitstempel und lassen den anderen Grad leer:

function(doc) 
{
        if(doc.begin && doc.end)        
        {
                //start and end time as UNIX timestamps (seconds, not milliseconds)
                var begin =  Math.round(new Date(doc.begin).getTime()/1000);
                var end =  Math.round(new Date(doc.end).getTime()/1000);

                emit(
                        {
                                type: "Point",
                                bbox : [0,start,0,end]                              
                        }, null
                );
        }
}

So lassen sich nun effiziente Bereichsabfragen durchführen:
http://localhost:5984/entries/_design/entries/_spatial/entriesByPeriod?bbox=0,1280998800,0,1281000600

{"update_seq":16,"rows":[
{"id":"s-ffc0b6b0-59d4-4a3b-ad36-7ec05e7db1de","bbox":[0,1280999512,0,1281000193],"value":null}
]}

Barrier points in Node.js

Node.js ist ein serverseitiges, hochskalierbares Framework für die Entwicklung von asynchronen Netzwerkanwendungen in JavaScript. Aufgrund der Asynchronität ist das Framework in der Lage, tausende Verbindung gleichzeitig offen zu halten und zu verarbeiten. Ein wesentliches Merkmal hier von ist, dass ausschließlich mit Callbacks gearbeitet wird, um auf Beendigungen von Operationen zu reagieren.

Im Normalfall führt das zu einem Chaining von Callback. Im folgenden Beispiel sollen zwei Dateien geöffnet werden und ihr kompletter Inhalt zurückgegeben werden:

var size = 0;

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (!err)
	{
		// first file read
		size += data.length;
		fs.readFile('/home/benjamin/file2', function(err, data)
		{
			if (!err)
			{
				// second file read
				size += data.length;
				fs.writeFile('/home/benjamin/size', size, function(err)
				{
					if (!err)
					{
							// both file read and content written to file
							sys.log('done');
						}
					});
			}
		});
	}
});

Das Problem hierbei ist, dass zwei Aktionen, die eigentlich parallel ablaufen könnten, nämlich das lesen beider Dateien, nacheinander ablaufen müssen. Für einen parallelen Ablauf ist eine zusätzliche Koordination notwendig, da die Resultate beider Aufrufe in Verbindung stehen. Hierfür habe ich mich an den CyclicBarrier Klassen von Java orientiert und eine einfache Klasse für die Koordination von asynchronen Callbacks geschrieben. Eine Barriere wird erzeugt unter Angabe der Anzahl von teilnehmenden Parties. Desweiteren können optional ein Callback für die Beendigung sowie ein optionaler Callback im Abbruchsfall registriert werden. Anschließend können die einzelnen Aufgaben der Parties angestoßen werden. Im Erfolgsfall müssen diese an der Barriere mit submit() anzeigen, dass sie beendet sind. Durch abort() kann auch ebenfalls ein Abbruch signalisiert werden. Eine Propagation der Ergebnisdaten lässt am besten durch eine externe Variable realisieren, auf den die Funktionen ebenfalls Zugriff haben.

Der Code des Beispielszenarios ändert sich dann wie folgt:

var size = 0;

var b = new Barrier(2, function()
{
	//Success callback
	fs.writeFile('/home/benjamin/size', size, function(err)
	{
		sys.log('done');
	});
}, function()
{
	//Aborted callback
	sys.log("aborted");
});

fs.readFile('/home/benjamin/file1', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

fs.readFile('/home/benjamin/file2', function(err, data)
{
	if (err)
	{
		b.abort();
	}
	else
	{
		size += data.length;
		b.submit();
	}
});

Wie im Vergleich zu erkennen ist, lässt sich die Ausführung deutlich beschleunigen. Ein paar Rahmenbedingungen gibt es jedoch. Zunächst dürfen die Teilaufgaben keine Abhängigkeiten untereinander besitzen. Dann darf ein Abbruch einer Teilaufgabe keine Auswirkung auf noch laufende Teilaufgaben besitzen, dessen Ergebnis später verworfen wird. Schließlich muss bei der Programmierung darauf geachtet werden, dass in jedem Fall eine Teilaufgabe mit submit() oder abort() terminiert, da ansonsten ein Lock entsteht.

Die Klasse ist relativ einfach:

/**
 * @class
 * 
 * Creates a new barrier for the given amount of parties. 
 * @param parties
 * @param barrierCallback
 * @param abortCallback
 * @return
 */
var Barrier =  function(parties, barrierCallback, abortCallback)
{
	this.parties = parties;
	this.barrierCallback = barrierCallback;
	this.abortCallback = abortCallback;

	this.running = true;
	this.count = 0;
};

/**
 * Signals a completion of one of the parties.
 * @return
 */
Barrier.prototype.submit = function()
{
	if (++this.count === this.parties && this.running)
	{
		this.barrierCallback();
	}
};

/**
 * Signals an abort by one of the parties. If not callback is passed, the default abort callback will be executed.
 * @param customAbortCallback Optional callback that should be executed due to the abort.
 * @return
 */
Barrier.prototype.abort = function(customAbortCallback)
{
	if (this.running && customAbortCallback)
	{
		customAbortCallback();
	}
	else if (this.running && this.abortCallback)
	{
		this.abortCallback();
	}
	this.running = false;
};

Klasse auf github: http://gist.github.com/464179

Kurzpräsentation – Node.js

Auf dem gestrigen Webmontag in Ulm habe ich Node.js vorgestellt, ein Framework für serverseitiges JavaScript für skalierbare Netzwerkanwendungen. Dabei hat es sich um eine eher kurze und oberflächliche Präesentation gehandelt, die die Grundidee des asynchroner I/O Operationen betonen sollte. Detailliertere Beiträge zu Node.js wird es aber hier in Kürze geben.

ioexception.de

Benjamin Erb [] studiert seit 2006 Medieninformatik und interessiert sich insbesondere für Java, Web-Technologien, Ubiquitous Computing, Cloud Computing, verteilte Systeme und Informationsdesign.


Raimar Wagner studiert seit 2005 Informatik mit Anwendungsfach Medizin und interessiert sich für C++ stl, boost & Qt Programmierung, Scientific Visualization, Computer Vision und parallele Rechenkonzepte.


David Langer studiert seit 2006 Medieninformatik und interessiert sich für Web-Entwicklung, jQuery, Business Process Management und Java.


Sebastian Schimmel studiert seit 2006 Informatik mit Anwendungsfach Medizin und interessiert sich für hardwarenahe Aspekte, Robotik, webOs, C/C++ und UNIX/Linux.


Timo Müller studiert seit 2006 Medieninformatik. Er interessiert sich allen voran für Mobile and Ubiquitous Computing, systemnahe Enwticklung und verteilte Systeme, sowie Computer Vision.


Achim Strauß studiert seit 2006 Medieninformatik. Seine Interessen liegen in Themen der Mensch-Computer Interaktion sowie Webentwicklung und UNIX/Linux.


Tobias Schlecht studiert seit 2006 Medieninformatik und interessiert sich vor allem für Software Engineering, Model Driven Architecture, Requirements Engineering, Usability Engineering, Web-Technologien, UML2 und Java.


Fabian Groh studiert seit 2006 Medieninformatik. Seine Interessengebiete sind Computer Graphics, Computer Vision, Computational Photography sowie Ubiquitos Computing.


Matthias Matousek studiert seit 2007 Medieninformatik und interessiert sich besonders für Skriptsprachen, Echtzeitsysteme und Kommunikation.


Michael Müller [] studiert seit 2009 Medieninformatik. Er interessiert sich vor allem für Web-Technologien, Ubiquitous Computing, User-Interfaces, UNIX und Creative Coding.


Falco Nogatz [] studiert seit 2010 Informatik mit Anwendungsfach Mathematik. Er interessiert sich für Web-Technologien, Programmierparadigmen und theoretische Grundlagen.

Archiv

Februar 2015
M D M D F S S
« Mrz    
 1
2345678
9101112131415
16171819202122
232425262728