ClojureScript

Artikel uit JAVA magazine 3 2021

Functioneel programmeren op de front-end met onveranderlijke datastructuren

 

Velen zullen bekend zijn met TypeScript een superset van JavaScript. Dit is ontwikkeld om de tekortkomingen van JavaScript op te lossen voor grote projecten. Zonder tegelijkertijd de compatibiliteit van JavaScript aan te tasten. Een van de grootste voordelen hiervan is de toevoeging van static typing, hierdoor komt programmeren op de front-end steeds dichter bij het programmeren op de back-end, zoals Java en C-Sharp.

 

Naast TypeScript bestaat er een soortgelijke optie voor functioneel programmeren, vergelijkbaar met talen uit de LISP familie. Het bekendste LISP dialect is Python. Deze optie heet ClojureScript.

 

Gecombineerd met React biedt het de mogelijkheid om minimalistisch en functioneel te programmeren, daarnaast krijg je er een optimale workflow voor front-end development bij.

Een workflow welke direct feedback geeft, zonder onnodige wachttijd.

 

{ De LISP familie & functioneel programmeren }

 

Anders dan JavaScript is Clojure een opinionated taal. Dat wil zeggen dat de taal je een bepaalde richting op duwt. In het geval van Clojure is dat richting van functioneel programmeren. Clojure doet dat onder andere door de basis datastructuren onveranderlijk te maken. Dat wil zeggen dat overschrijven simpelweg niet mogelijk is. Wat hiermee wordt bewerkstelligd is het uitsluiten van side effects, namelijk functies die lokale state variabelen aanpassen.

 

Hiermee komen we een stap dichterbij pure functies. Dit zijn functies die op basis van de input altijd dezelfde output zullen teruggeven. Pure functies vormen de basis voor functioneel programmeren. In JavaScript kan hetzelfde bereikt worden, maar kost het meer moeite om soortgelijke principes toe te passen.

 

De onderstaande quote zal je dan ook niet verbazen:

 

“When you know your data can never change out from underneath you, everything is different.” – Rich Hickey, bedenker van Clojure.

 

 

{ Wat is de REPL en wat kun je er mee doen? }

REPL staat voor read-eval-print-loop. Deze wordt hieronder stap voor stap beschreven. De REPL kan één of meer expressies verwerken in tegenstelling tot een hele compilation unit.

 

Read

De read functie neemt een expressie en zet het om naar een datastructuur in memory, zie listing 1.

 

(+ 1 2 3)

Listing 1.

 

Eval

De eval functie neemt de datastructuur en evalueert het. Omdat het een LISP expressie is, evalueert deze bij het begin van de functie, hierdoor wordt de functie aangeroepen op de argumenten: 1 2 3. Waar dus 6 uit komt. Dit is mogelijk doordat de LISP programmeer talen toegang hebben tot hun eigen evaluator. Dit is uniek aan deze taal en maakt het dus mogelijk concepten als de REPL te gebruiken.

 

Print

De print functie neemt het resultaat van de eval en print het naar de gebruiker. Complexe output wordt op basis van de content netjes getoond.

 

Loop

De ontwikkelomgeving geeft dan de read state terug, die een loop genereert welke eindigt wanneer het programma gesloten wordt.

 

{ Praktisch gebruik van de REPL }

Clojure development in combinatie met de REPL vereist een specifieke setup, de meest gebruikte is de Cursive plugin voor IntelliJ. Wanneer je een functie schrijft, kun je deze direct evalueren. Enige wat je hoeft te doen is de namespace en imports naar de REPL te sturen. Vervolgens selecteer je de functie en functie call en stuur je deze naar de REPL, dit kan met het rechtermuisknop menu of de sneltoets cmd+shift+p. Daarin kun je direct zien wat je functie doet. Later in dit artikel zal ik terugkomen op het gebruik hiervan in jouw front-end development workflow.

 

{ Een greep uit ingebouwde Clojure functies }

 

 

((and (= 1 1) +) 1 2 3)

; => 6

 

((first [+ 0]) 1 2 3)

; => 6

 

Listing 2.

 

In het eerste voorbeeld van listing 2 wordt de ingebouwde “and” functie gebruikt. Hier is de return value de eerste falsey of laatste truthy waarde. In dit geval is de return de plus omdat het de laatste truthy waarde is en wordt vervolgens toegepast op argumenten: 1 2 3 dit geeft 6 terug. In het tweede voorbeeld van listing 2 wordt de ingebouwde “first” functie gebruikt. Hier is de return value het eerste element in de reeks, in dit geval de plus, die dus ook 6 teruggeeft.

 

{ Andere voordelen van Cojure }

Een groot voordeel van Clojure is dat je zelf macro’s kunt schrijven, omdat Clojure het toestaat de structuur te manipuleren voordat deze het zelf evalueert. Dat is bij andere programmeertalen niet mogelijk. Een handige macro zou kunnen zijn een console log functie die zowel je resultaat print als terug geeft. Deze functie zou er in Clojure uitzien zoals in listing 3:

 

(let [result expression]

(println result)

result)

 

Listing 3.

 

Om een macro te maken is het enige wat we hoeven te doen defmacro voor de functie zetten. Ook is er op de eerste regel ruimte voor een string die uitleg geeft. Zie het eindresultaat in listing 4.

 

(defmacro my-print

“function that both returns and prints the arguments given”

[expression]

(list ‘let [‘result expression]

(list ‘println ‘result)

‘result))

 

Listing 4.

 

Dit geeft de mogelijkheid het gedrag van de programmeertaal aan te passen, wat zorgt voor schone abstracte code. In plaats van het schrijven van veel functies om data op een bepaalde manier te interpreteren, kun je daar in Clojure een macro voor schrijven. Dit is natuurlijk ook gelijk een groot gevaar, wanneer dit niet goed gedocumenteerd wordt en verkeerd gebruikt wordt. Een ander groot voordeel is java interoperability, en dat betekent dat we handige functies van Java kunnen lenen.

 

{ Clojure in de front-end }

 

ClojureScript wordt gebruikt voor front-end development. ClojureScript website: “ClojureScript is een compiler voor Clojure op JavaScript. Het emit JavaScript code welke compatible is met de geavanceerde compilatie mode van de Clojure optimizing compiler”

 

Dit biedt dus alle voordelen zoals eerder beschreven, maar dan voor de front-end. Laten we beginnen met een simpel voorbeeld, namelijk: een React component. React is een veel gebruikt front-end framework. Met een component splits je de UI op in onafhankelijke en herbruikbare delen. De meest eenvoudige manier om een component te definiëren in React is een functie die HTML teruggeeft.

 

In listing 5 en 6 zien we voorbeelden van een React component. Listing 5 in Clojurescript, listing 6 in JavaScript. Het valt gelijk op dat de HTML notatie uit listing 5 anders is. Dit komt omdat Clojure er op deze manier een list van kan maken. Deze domeinspecifieke HTML notatie noemen we HICCUP.

 

 

(defn component

[] [:div

“Hallo, wereld!”])

 

Listing 5.

 

function component() {

return (

<div>

“Hallo, wereld!”

</div>

);

}

 

Listing 6.

 

Het is duidelijk dat listing 5 een stuk minder code bevat. Maar wat biedt de HICCUP notatie nog meer voor voordelen?

 

  • In listing 6 zie je een stuk JavaScript code dat HTML code terug geeft. ClojureScript uit listing 5 maakt geen onderscheid en geeft enkel een keyword, string of een lijst terug
  • Het is efficiënt, duidelijk en leesbaar. Er is geen overbodige code, zoals bijvoorbeeld HTML closing tags. Deze worden afgesloten zoals we dat bij functies zien.

 

{ Hoe werkt dit? }

Hiervoor moeten nog een aantal Clojure concepten onder de loep genomen worden. Omdat Clojure een LISP dialect is, maakt het gebruik van vectors en sequences.

 

  • [ ] is een vector die lijkt op een ArrayList
  • () is een sequence die lijkt op een LinkedList

 

Je kunt in Clojure een lijst maken zoals in listing 7.

 

[1 2 3 4] [“hello” “world”] [“my” “list” “contains” 10 “things”] ; Het maakt niet uit wat

; je in een Clojure list zet!

 

Listing 7.

 

 

‘(1 2 3 4)

‘(“hello” “world”)

 

Listing 8.

 

Voor sequences werkt het iets anders zie listing 8. Waarom zit er een ‘ voor de sequence? Zonder de “ ‘ “ is deze sequence een functie. Eerste element uit deze sequence wordt dan de functie zelf, de daarop volgende elementen argumenten zoals in listing 9.

 

(+ 1 2 3 4)               ; -> 10

(str “hello” ” ” “world”) ; -> “hello world”

 

(println “hi!”)           ; print “hi!” naar de console

(run-my-function)         ; voert `run-my-function` uit

 

Listing 9.

 

Als je nog een keer naar het ClojureScript voorbeeld in listing 5 kijkt, kun je nu herkennen dat het een functie is met daarin een Clojure list, niet te verwarren met een html list item.

Laten we er een iets uitgebreider voorbeeld bij pakken:

 

[:div

[:h1 “This is a header”] [:p “And in the next element we have 1 + 1”] [:p (+ 1 1)]]

 

Listing 10.

 

Hier in listing 10 zie je een div element met geneste elementen. Het div element is een lijst. In deze lijst zitten nog drie lijsten. Zo simpel is het en dat zie je terug in de notatie.

 

Na het lezen van de uitleg hierboven zul je ook een functie herkennen in listing 10. Deze kan direct in de lijst, omdat Clojure alles interpreteert als een lijst of sequence hoef je hier verder niets extra’s voor te doen.

 

Componenten en HTML elementen worden door Clojure geïnterpreteerd als simpele lijsten/objecten. Hierdoor kun je met deze elementen omgaan zoals dat je met data omgaat. Dit geeft enorm veel flexibiliteit, maar ook een vorm van abstractie die je nergens anders zult vinden.

 

{ Development workflow }

Een veelgebruikte library voor ClojureScript is Reagent in combinatie met shadow-cljs. Deze libraries zorgen ervoor dat Clojure met de browser kunt communiceren en dat je React kunt gebruiken. Wanneer je na het genereren de app opstart, zie je het volgende bericht in je console:

 

shadow-cljs – config: /Users/MAW30004/breaking-bad-quotes/shadow-cljs.edn

shadow-cljs – HTTP server available at http://localhost:8700

shadow-cljs – server version: 2.11.18 running at http://localhost:9630

shadow-cljs – nREPL server started on port 8777

shadow-cljs – watching build :app

 

Je ziet direct dat er dus ook een REPL server draait. Hier kunnen we naar verbinden met de Cursive plugin voor IntelliJ.

 

In listing 11 zie je links een React component waarin de tekst “Hello world” als state gezet wordt. De rechterkant van het scherm is de REPL omgeving. De geselecteerde tekst is een simpele functie die deze appstate naar de console logt.

Listing 11.

 

Normaal zou je:

  1. Het bestand opslaan
  2. Wachten tot de code opnieuw gecompileerd is
  3. Wachten op browser refresh
  4. Browser checken op de console log.

 

De ClojureScript manier:

  1. De code selecteren
  2. Command + shift + p om deze code naar de REPL te sturen.

 

Wat gebeurt er in de browser console? Zie listing 12. Je ziet direct de output verschijnen.

Door deze directe feedback voorkom je handmatig testen van functies, wat het werk een stuk betrouwbaarder maakt. Ook voorkom je onnodige wachttijd in je workflow, wat weer een hoop tijd bespaart.

Listing 12.

 

Je evalueert je functie met directe feedback en ook je appstate wordt geïnterpreteerd als een lijst. Zoals eerder beschreven zijn Clojure lijsten immutable, je mag deze lijsten dus niet muteren. Hierdoor word je gedwongen met een ander perspectief te kijken naar het ontwikkelen van je front-end component.

 

{ Conclusie }

We zijn begonnen met een introductie van Clojure een LISP dialect. Daar hebben we gezien dat deze programmeertaal een mening heeft en de programmeur de kant van het functioneel programmeren op duwt. in Clojure is het mogelijk direct code te evalueren door middel van de REPL.

 

Vervolgens hebben wij Clojurescript besproken. Dit is de front-end variant van Clojure hiermee is het mogelijk HTML, CSS en React Componenten te schrijven. Zonder dat de voordelen van Clojure verloren gaan. Met behulp van Reagent en Shadow CLJS heb je binnen de kortste keren een front-end draaien in Clojurescript met een leuke workflow en snelle feedback loop. Ben je enthousiast geworden? Check deze github pagina:

https://github.com/Awadje/clojure-article-java-magazine

 

 

 

 

Referenties

https://www.toptal.com/clojure/clojurescript-tutorial-react-front-end

https://www.braveclojure.com/getting-started/

 

 

BIO

 

Michael Awad is sinds maart 2020 ben ik werkzaam bij JSRoots, de front-end afdeling binnen Ordina. Naast Front-end ontwikkelaar is hij ook Security Champion. Hij heeft zijn meeste ervaring opgedaan in VueJS and Angular.