317875

Funktionale UIs mit Om

Von am 05.08.2014

In meinem letzten Beitrag habe ich von Facebooks React berichtet, um die View in Web-Apps einfacher beschreiben zu können. In diesem Artikel berichte ich nicht von etwas völlig anderem und werfe React somit über den Haufen, sondern zeige euch Om, eine Library die auf React aufbaut.

Zur Erinnerung: React ist eine JS-Bibliothek, welche Daten (App-State) in den Browser render, zum Beispiel ein Array mit Kommentaren. Ändern sich diese Daten (postet jemand ein Kommentar), wird diese Änderung im Browser angezeigt. Traditioneller Weise müssten wir hier mit .appendChild und der Gleichen arbeiten. Dies übernimmt jedoch React für uns, welchem wir nur beschreiben, wie unsere Liste mit Kommentaren zu jedem Zeitpunkt in der Lebenszeit der Webseite im Browser auszusehen hat.
Zusammenfassend lässt sich also sagen: React hält den App-State und die View (DOM) synchron.

Wo kommt nun Om ins Spiel? Nun, vor einiger Zeit bin ich von JavaScript abgewandert und auf ClojureScript umgestiegen. In einem meiner früheren Artikel habe ich darauf aufmerksam gemacht, dass JavaScript vollkommen ungeeignet ist, um ganze Applikationen damit zu programmieren. Das haben auch andere erkannt, weshalb sich immer mehr Sprachen etablieren, welche zu JavaScript kompilieren (GWT, CoffeScript, …). Diese Sprachen bieten einen Mehrwert und Abstraktionen, welche mit reinem JavaScript nur umständlich zu verwirklichen sind. Ich habe mich für ClojureScript entschieden, da ich Clojure (kompiliert zu Java-Bytecode) schon von der Server-Seite her kannte.

Exkurs ClojureScript:

// JavaScript
var x = 5 + 5;
var o = {y: x};
var inc = function (i) { return i + 1; };
o.y === x;
inc(x);

// ClojureScript
(let [x (+ 5 5)
      o {:y x}
      inc #(+ 1 %)]
  (= (:y o) x)
  (inc x))

Diese Beispiele sollen JavaScript-Code und ClojureScript gegenüberstellen. ClojureScript ist eine Lisp-Sprache, dass heißt jedes Programm ist gleichzeitig eine Datenstruktur. JavaScript-Programme schreiben wir mit Text. var x = 5 + 5; ist nur Text, welcher von der JavaScript-Engine ausgeführt wird. In der ClojureScript-Variante fallen die verschiedenen Klammern auf. Diese öffnen und schließen Datenstrukturen. Runde Klammern sind Listen, eckige sind Vectoren und geschwungene Klammern sind Maps. Das heißt unser Lisp-Programm ist eigentlich ein Array und kein Text. Dies eröffnet Möglichkeiten für Macros und Meta-Programming, da wir Lisp-Code laden und wie Arrays bearbeiten können. Wenig überraschend kommt Lisp aus dem Forschungs-Bereich der künstlichen Intelligenz, da dies erlaubt den eigenen Code zu inspizieren, beliebig umzuschreiben und neu auszuführen (sich selbst verändernde Programme).
Ein weiteres Merkmal von ClojureScript ist, dass unsere Datenstrukturen unveränderlich sind. In unserem Beispiel oben bedeutet dies, dass wir o.y (oder o und x) nicht überschreiben können. Jede Veränderung führt zu der Entstehung einer Kopie der Datenstruktur, welche die Änderungen beinhaltet. Zusätzlich gibt es keine Klassen und Objekte. ClojureScript ist keine Objekt-orientierte Sprache, sondern eine funktionale Sprache. State und Funktionalität werden nicht in Klassen vereint, sondern Funktionen erhalten Werte und liefern Werte zurück, ohne einen State zu manipulieren.
Hiermit will ich es auch mit der Beschreibung von ClojureScript belassen. Sollte sich jemand mehr dafür interessieren, kann ich gerne noch weitere Artikel zu dieser Sprache schreiben.

Om ist ein Interface zu React in ClojureScript. Das heißt wir können React-Componenten einfach in ClojureScript erstellen.

Sehen wir uns Mal an, wobei es uns helfen kann. Als Beispiel für diesen Artikel wollen wir die neuen HTML5 Input-Sliders verwenden, um ein Farbfeld live mit RGB-Slidern verändern zu können. Um die Sache mit der Synchronisation noch interessanter zu machen, verdoppeln wir die Slider und Farbfeld und versuchen Änderungen in beiden zu reflektieren. Als Bonus wollen wir alle Änderungen in einer History beobachten können. Hier mal eine Skizze:

1

Wie sehen nun unsere Abhängigkeiten aus? Es muss ja schließlich alles synchronisiert sein. Wenn ich den Slider für Rot verändere, muss sich der zweite “Rot”-Slider mitbewegen, es muss sich das Farbfeld aktualisieren und es muss ein neuer Eintrag in der History erstellt werden. Wenn man nun alle Abhängigkeiten einträgt, ergibt sich ein Bild wie folgt:

Untitled1

Bei diesem Anblick kann einem schon das Schaudern kommen, wenn man bedenkt, was man alles synchron halten muss.

Eine Implementierung in JavaScript könnte so aussehen (Klick auf Result):

Es ist sicherlich nicht die beste Implementierung, aber auch nicht die schlechteste. Wir haben eine redraw-Funktion, in welcher wir alle Elemente mit den aktuellen Daten aktualisieren. Wir rufen redraw() bei jeder Veränderung auf. Dies alles zu machen ist etwas mühsam und mit mehr Elementen wächst die redraw-Funktion immer weiter und weiter, was eigentlich nicht sehr modular ist.

Dieses Beispiel habe ich in ClojureScript und Om reimplementiert.

Auch wenn man nicht so viel von ClojureScript versteht und auf “Source” klickt, sieht man, dass es nicht “die eine, große redraw-Funktion” gibt. Die Applikation ist in modulare Komponenten unterteilt. Um das Farbfeld zu verdoppeln, habe ich nur den bestehenden Component ein zweites Mal eingefügt (eine Zeile Arbeit) und an die selben Daten geknüpft. Alle Komponenten reflektieren automatisch den State mit dem sie verknüpft wurden und halten sich synchronisiert. So Programme zu schreiben ist meiner Meinung nach einfacher und sinnvoller, als pures JavaScript.

Am Schluss des Artikels angekommen, wäre ein besserer Titel vielleicht “Warum ich von JavaScript auf ClojureScript mit Om umgestiegen bin” gewesen. In einem Artikel kann man nur schwer auf ClojureScript und Om gleichzeitig in Form von ausführlichen Tutorials eingehen. ClojureScript muss man schon für zwei Wochen gelebt haben, um die Vorteile wirklich greifbar zu machen. Wenn das eher nichts für euch ist, hat euch dann hoffentlich das Beispiel ein wenig gezeigt, dass man sich über Abhängigkeiten, Modularität und der Synchronisation von State bei Applikationen Gedanken machen muss … und HTML5 Slider in Aktion. Ich empfehle euch, das Beispiel selber mal in JavaScript auszuprogrammieren. Da bin ich gespannt, auf welche Lösungen ihr kommt. Postet einfach ein Kommentar mit Link.

The comments are closed.