Naar het artikeloverzicht

[Blog]

Het HelloData platform heeft als doel om slimme meters, apps en consumenten met elkaar te verbinden en te zorgen dat de consument eigenaar blijft van zijn data en hierover de controle behoudt. Op technisch vlak zorgt dit voor uitdagingen wat betreft datastromen (schaalbaarheid), beveiliging (toegangsrechten; bescherming tegen malafide gebruikers) en de interface tussen de bron (bijvoorbeeld de slimme meter) en services (de apps die de meetdata gebruiken).

De eerste versie van HelloData, die geschreven was in Ruby on Rails, ondervond prestatieproblemen en daarom besloten wij het platform te (her)bouwen in Clojure.

Waarom Clojure? In deze blogpost volgen vier redenen.

REDEN 1: Clojure is een dialect van Lisp

Aan het begin van mijn carrière als softwareontwikkelaar kwam ik voor het eerst in aanraking met Lisp via het artikel Beating the Averages van Paul Graham (2001). In dit artikel beschrijft Graham hoe zijn startup (Viaweb, later Yahoo Stores) succesvol werd middels een “geheim wapen”.

Dit geheime wapen was Lisp; een familie van programmeertalen die zijn oorsprong kent in 1958 en wordt gekenmerkt door vele haakjes openen en haakjes sluiten.

Bron: https://xkcd.com/297
Bron: https://xkcd.com/297

Met dit artikel wist Graham mij ervan te overtuigen dat Lisp het krachtigste model van computatie is dat er bestaat. En dat ik met mijn Java-bril (Java is de meest gebruikte programmeertaal waarin ook ik begon) aan de “Blub Paradox” leed: als programmeur dacht ik in de programmeertaal Blub en kon ik alleen talen begrijpen die minder krachtig zijn dan Blub. Krachtigere talen dan Blub, zoals Lisp, deed ik af als onzinnig vanwege mijn vastgeroeste Blub-gewoontes van geest.

Zo begon mijn pad om Lisp te leren.

Ik vulde avonden en weekenden met geestverruimende oefeningen die mijn gedachten herstructureerden. Ik moest eerst vooral wennen aan de haakjes. En toen snapte ik het1.

1Dit moment van verlichting is geïllustreerd in een college van Gerald Jay Sussman op het moment dat hij klaar is met het uitschrijven van Lisp-code, passend op één schoolbord, waarin elke programmeertaal kan worden uitgedrukt. Zie https://youtu.be/0m6hoOelZH8?t=34m34s.

De kracht van Lisp zit in de haakjes.

De fundamentele datastructuur in Lisp (van LISt Processor) is de lijst. De data waarmee de Lisp programmeur werkt bestaat uit lijsten omringd door haakjes, maar de code zelfook. Dit betekent dat code als data kan worden behandeld en andersom. Met een moeilijk woord heet dit homoiconiciteit.

Homoiconiciteit maakt het mogelijk om transformaties te maken van broncode, programma’s te schrijven die programma’s maken – in Lisp heetten deze programma’s macro’s – en nieuwe syntax te introduceren.

Dat is wat Lisp zo krachtig maakt en waarom Paul Graham het omschrijft als het “geheime wapen”.

In de HelloData praktijk maken wij op twee manieren gebruik van deze kracht van Lisp:

  1. We trachten binnen Clojure een “eigen taal” te schrijven die ons domein omschrijft. Door de flexibiliteit van abstracties worden we niet tot nauwelijks gehinderd door beperkingen van een programmeertaal.
  2. De flexibiliteit maakt het mogelijk om eigenschappen van andere talen te integreren in Lisp, waardoor deze als bibliotheken (gedeelde code van anderen waardoor een ontwikkelaar deze niet zelf hoeft te schrijven) beschikbaar zijn.
    Een voorbeeld hiervan zijn Go-routines uit de programmeertaal Go, die in Clojure via de core.async bibliotheek kunnen worden geïntroduceerd. Het verschil met Go zelf is dat dergelijke taaleigenschappen in Clojure geen fundamentele eigenschap zijn van de taal.

Brengt Lisp alleen maar rozengeur en maneschijn?

Nee.

Dat Lisp zo krachtig en flexibel is, zorgt ervoor dat er iets ontstaat dat ook wel de vloek van Lisp wordt genoemd (zie ook Rudolf Winestocks essay The Lisp Curse); omdat het zo gemakkelijk is om te doen, zijn er ook veel taaluitbreidingen. Dit zorgt voor vele dialecten en bibliotheken die half af en slecht gedocumenteerd zijn en eigenlijk alleen geschikt zijn voor het specifieke domein van de maker.

Clojure is een Lisp dialect dat in mindere mate last heeft van deze vloek van Lisp. In Clojure wordt de consistentie van de kernbibliotheken en de standaard manier van doen bewaakt door de maker Rich Hickey en zijn bedrijf Cognitect.

Verder heeft Clojure toegang tot de Java Virtual Machine (JVM) en, in de vorm van ClojureScript, tot JavaScript, waarop veel standaardbibliotheken beschikbaar zijn. Zo komen we bij de tweede reden voor onze keuze voor Clojure.

REDEN 2: Clojure draait op de JVM en JavaScript runtime

De Java Virtual Machine is een platformonafhankelijke omgeving (de JVM werkt op Linux, Windows, macOS, et cetera) voor het uitvoeren van Java-bytecode. De JVM bestaat sinds de jaren ‘90 en is sindsdien door velen geoptimaliseerd.

Op de JVM is een overvloed aan bibliotheken beschikbaar. Van webservers tot gebruikersinterfaces; je kan het zo gek niet bedenken of er is een bibliotheek voor. Clojure wordt omgezet naar Java-bytecode. Daarmee profiteert Clojure van de stabiliteit, populariteit, snelheid en veiligheid van de JVM.

Alle bibliotheken op de JVM zijn vanuit Clojure simpel te gebruiken. Zo wilden wij in onze registratieflow gebruikmaken van Macaroons: uitwisselingssleutels beschreven in een paper van Google. Macaroons hoefden wij zelf niet te implementeren, omdat er al een Java-bibliotheek was die we – afgezien van een spelfout – gemakkelijk konden gebruiken.

Wat Clojure doet op de JVM doet ClojureScript met JavaScript. JavaScript is de meest gebruikte programmeertaal ter wereld. Het draait namelijk in vrijwel alle browsers.

Hoewel de namen op elkaar lijken hebben Java en JavaScript net zo weinig met elkaar te maken als een kast en een kasteel. Clojure en ClojureScript daarentegen zijn dezelfde taal. Dit betekent dat, zolang je geen gebruikmaakt van bibliotheken specifiek voor de host, je de Clojure code van de backend opnieuw kan gebruiken op de frontend. Hiermee kan je een volledige applicatie in Clojure schrijven en bijvoorbeeld de validatiecode opnieuw gebruiken in de frontend en backend.

Het mooiste van ClojureScript? De unieke ontwikkelervaring.

Gebruikelijk gaat het ontwikkelen van websites in de volgende twee stappen:

  1. Pas code aan.
  2. Ververs de browser om wijzigingen te zien.

Om stap 1 kunnen we lastig heen, maar stap 2 is vertragend en vervelend. De ontwikkelaar moet namelijk een extra handeling verrichten en is de opgebouwde toestand in de website kwijt. Als er een paar keer is geklikt en velden zijn ingevuld om de website te testen is de ontwikkelaar bij een verversing weer terug bij af.

De bibliotheek Figwheel biedt een oplossing die het continue verversen overbodig maakt. Figwheel zorgt ervoor dat de code die is aangepast direct naar de browser wordt gestuurd.

Wanneer je links in het beeld de browser hebt en rechts de code die je aanpast, dan zie je bij een codeaanpassing rechts meteen het effect op de website links. Zie onderstaand filmpje voor een demonstratie door de maker van Figwheel.

Dat is de ontwikkelervaring die wij bij HelloData hebben en dat is een van de redenen waarom het ontwikkelteam zo blij kijkt.

Clojure draait dan wel op de JVM, maar de gescheven code lijkt in de verste verte niet op Java: de meest populaire taal op de JVM. Dit is zo vanwege de haakjes en hogere niveau van abstractie, maar vooral omdat Clojure data-georiënteerd is. Wat dit is lees je in het volgende punt.

REDEN 3: Clojure is data georiënteerd

De Wet van Moore stelt dat de prestaties van processoren elke anderhalf jaar verdubbelen. Vanwege fysische limieten is deze Wet van Moore sinds ongeveer 2005 dood.

In Clojure is veel minder code nodig om dezelfde dingen uit te drukken dan in talen als Java. Geschreven code lijkt alleen over het probleem te gaan dat er mee wordt opgelost. Syntax, ontwerppatronen en andere ceremonieën die niet over het probleem gaan verdwijnt. Dit komt omdat Clojure data behandeld als data. Wat betekent dat? Programmeurs werken met data. In vele andere programmeertalen, zoals bijvoorbeeld Java, wordt data niet behandeld als data. Voordat er met data wordt gewerkt wordt het namelijk eerst omgezet naar een door de programmeur gecreëerde specifieke datastructuur (class). Deze ad-hoc datastructuren representeren het beeld van de wereld van die programmeur die hem heeft gecreëerd. Dit maakt datastructuren verschillend, inconsistent en idiosyncratisch. De datastructuren zijn specifiek voor dat geval, niet uit te breiden, en maken het vaak lastig om een tweede implementatie te creëren.

In Clojure gebruik je voor data vaak gewoon Clojure datastructuren (lijsten; vectoren; hash-maps; setten).

Dit betekent in de praktijk dat je transformaties uitvoert op Clojure datastructuren met de vele beschikbare functies die daarbij komen.

“It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.” – Alan Perlis (Epigrams in Programming)

Om het punt in te laten zinken kan ik je adviseren een paar minuten te kijken, vanaf 51 minuut en 15 seconden in onderstaande video. Deze video bevat hoogdravende taal (een “rant”) van Rich Hickey, de maker van Clojure, waarbij hij een specifieke Java-datastructuur (het modelleren van een verzoek van een browser) vergelijkt met de generieke Clojure-representatie (de standaard hash-map – je geeft een naam en je krijgt een waarde).

De data-oriëntatie (behandeling van data als data) is – naast het te bereiken niveau van abstractie – de reden waarom Clojure code bases relatief klein zijn. Hoe kleiner je programma hoe minder bugs. En vrijwel alle code gaat over het probleem dat je oplost en niet over iets anders. Dit maakt Clojure code simpel en robuust.

Dus, de flexibiliteit, host platformen en behandeling van data als data, zijn drie redenen om Clojure te gebruiken. Een ander belangrijk punt is dat Clojure gemaakt is voor parallel programmeren.

REDEN 4: Clojure omhelst parallel programmeren

De Wet van Moore stelt dat de prestaties van processoren elke anderhalf jaar verdubbelen. Vanwege fysische limieten is deze Wet van Moore sinds ongeveer 2005 dood.

Bron: IEEE Computer Society
Bron: IEEE Computer Society

In de bovenstaande grafiek is te zien dat de limiet op prestaties van processoren is bereikt. Op dit moment worden er technieken bedacht om computers toch sneller te maken, namelijk:

  1. Meer processoren op een chip (multikernprocessoren, zie onderste lijn in de grafiek).
  2. Inzetten van meerdere computers, bijvoorbeeld in de cloud (gedistribueerd programmeren).

De toekomst van programmeren is daarmee multikern en gedistribueerd2.

2Er wordt uitgebreid onderzoek gedaan naar kwantumcomputers en de resultaten zijn veelbelovend. Kwantumcomputer en het programmeren ervan wordt in deze blogpost buiten beschouwing gelaten.
Om het maximale uit multikernprocessoren en gedistribueerde machines te halen, dienen softwareontwikkelaars parallel te programmeren. Als de broncode sequentieel is, kan je het immers niet uitvoeren op meerdere computers of processoren tegelijk.

Het probleem met parallel programmeren?

Het is in de meeste programmeertalen moeilijk.

Parallel programmeren is lastig vanwege zogenaamde race condities. Race condities treden op als twee processen tegelijkertijd gedeelde data willen lezen of schrijven en de volgorde of timing hiervan oncontroleerbaar is. Ze leiden tot vele bugs en zijn vaak lastig te vinden.

Race condities manifesteren zich als af en toe een foutieve situatie.

Een manier om race condities te voorkomen is het aangeven van kritieke secties op plekken in de broncode die maar door één proces tegelijk mogen worden aangesproken. Het programmeren met deze kritieke secties veroorzaakt echter zelf weer problemen. Neem bijvoorbeeld deadlocks, waarbij het ene proces wacht op de andere en andersom, en de software bevriest. Of starvation waarbij een proces nooit aan de beurt komt en maar blijft wachten. Naast deze problemen is de code met kritieke secties niet parallel.

De maker van Clojure zegt in zijn presentatie Clojure Concurrency dat we de volgende twee dingen kunnen leren van een beroemd Java-boek met technieken om parallel te programmeren:

  1. Parallel programmeren in Java is onvoorstelbaar moeilijk.
  2. Onveranderlijkheid van waardes (immutability) wordt keer op keer genoemd als oplossing.

Race condities ontstaan door veranderingen: het ene proces wil een waarde schrijven, terwijl het andere proces leest of twee processen tegelijk willen schrijven. Maar wat als toegewezen waardes onveranderlijk zijn? Dan wordt er niet meer geracet.

Onveranderlijkheid van waardes (immutability) maken kritieke secties overbodig en race condities onmogelijk.

Zogenaamde functionele programmeertalen zijn op dat principe gebaseerd. Net als bij wiskundige functies zijn uitkomsten van een functionele programmeertaal altijd hetzelfde. In niet-functionele programmeertalen kun je niet zeker zijn over uitkomsten; er kan immers een ander proces of bijeffect zijn dat een waarde op de achtergrond verandert. Bij functionele programmeertalen kan je wel zeker zijn (dit heet referentiële transparantie). Daarom maakt het niet uit in welke volgorde je code uitvoert en dat maakt parallellisatie makkelijk.

Clojure levert gereedschap om functioneel te programmeren en onveranderlijke waardes zijn fundamenteel ingebouwd in de taal. Een eenmaal toegewezen waarde is in Clojure onveranderlijk (persistent). Als je iets wilt wijzigen dien je kopieën te maken van eerdere datastructuren met de wijziging toegevoegd. Om te voorkomen dat al deze datastructuren veel ruimte innemen in het geheugen van de computer maakt Clojure gebruik van een methode genaamd “structureel delen”. Dit leidt tot efficiënte persistente datastructuren. Zie onderstaand plaatje ter verduidelijking.

Bron: Functional Advantage with Clojure
Bron: Functional Advantage with Clojure

In Clojure lijkt het voor de programmeur alsof hij of zij werkt met kopieën van data, maar onder de motorkap hergebruikt Clojure alles wat onveranderd is. Daardoor gebruikt Clojure ondanks alle kopieën minimaal geheugen.
Soms wil je echter een waarde wel veranderen. In de echte wereld veranderen er immers dingen en in een praktische programmeertaal dien je te kunnen werken met veranderlijke waarden.

Veranderlijke waardes zijn in Clojure wel mogelijk en daarmee is de taal niet puur functioneel. Clojure biedt constructen voor verandering. In Clojure zijn mechanismen ingebouwd om waardes te wijzigen die ervoor zorgen dat het systeem altijd consistent blijft. Dit alles zonder dat de ontwikkelaar handmatig hoeft te werken met foutgevoelige kritieke secties.

Met deze focus op functioneel programmeren, persistente datastructuren en taalconstructen omhelst Clojure parallel programmeren.

En zo zijn er nog veel meer redenen om te kiezen voor Clojure!

De oorsprong in Lisp, de ondersteuning van host platformen en de focus op parallel programmeren zijn drie redenen waarom HelloData wordt gebouwd in Clojure.
Hierbij heb ik nog gezwegen over:

De toekomst is parallel en Clojure is er klaar voor.

Er is alleen 1 probleem met Clojure. En alleen jij kan dat probleem oplossen.

Naast aanpassingsproblemen van programmeurs met een andere achtergrond, is er een probleem met het vinden van ervaren Clojure ontwikkelaars.

Ben jij een ervaren Clojure ontwikkelaar? En wil je bijdragen aan een duurzame toekomst? Bij HelloData verwelkomen we je graag.

Neem contact met me op. Ik vertel je graag meer over HelloData.

Reacties over "Waarom HelloData in Clojure wordt gebouwd "

  • Er zijn nog geen reacties geplaatst bij dit artikel

Reageer