En funksjonell Stack-basert kalkulator


fredag 29. april 2011 Clojure Video Kata TDD

NNUG-møtet i Bergen på onsdag ble jeg inspirert til å gjøre en stack-basert kalkulator-kata. Det var nok ikke det som var foreleserens intensjon, men dette var det mest spennende jeg fikk ut av foredraget.

Siden onsdagen har jeg gjort denne kataen tre ganger, i Clojure, med ganske ulike resultat hver gang. Den siste gangen filmet jeg det jeg gjorde, la på litt musikk, og her er resultatet.

Men les gjerne det som står nedenfor først, sånn at du vet hva du går til…

Stack-based Calculator Kata in Clojure from Torbjørn Marø on Vimeo.

Ren og skitten kode

Det som kanskje er litt spesielt er at jeg implementerer kalkulatoren med funksjonell programmering og “rene” funksjoner. Det vil i praksis si at koden ikke kan pushe eller poppe data på/av stacken. Funksjonene må ta inn en stack som parameter, returnere en ny stack tilbake som programmet kan bruke videre, og ellers ikke gjøre noen ting.

Denne implementasjonen illustrerer derfor seperasjonen mellom “ren” kode uten bieffekter og “skitten” kode med bieffekter, slik som jeg snakket om i blogposten hemmeligheten bak funksjonell programmering avslørt.

TDD

I videoen bruker jeg tester som støtte når jeg implementerer den rene delen av programmet. Men dette var som sagt tredje gangen jeg løste oppgaven, og jeg følte meg ganske sikker. Derfor ble testene kanskje ikke så gode, dvs. de drev ikke utviklingen på samme måte som første og andre gangen.

Strengt tatt kunne jeg ha droppet en av testene jeg brukte også, for den var veldig lik en av de andre. Men det oppdaget jeg ikke i farten…

å forstå Clojure-koden

Koden som demonstreres i videoen er egentlig ganske basic, og bør passe for dem som er relativt ferske i Clojure. Her er noen tips som kan gjøre det enklere å følge med.

Den beste datatypen å bruke for å representere en stack i Clojure er en vector. Det er en slags array som er optimalisert for å legge til elementer på enden. Man kan opprette en tom vector slik: [], eller en vector med noen numre slik: [1 2 3 4 5].

For å legge til et nytt element på stacken brukes funksjonen conj. (conj [1 2 3] 4) returnerer en ny vector: [1 2 3 4].

I denne tredje implementasjonen valgte jeg å bruke det som kalles destructuring – en form for pattern matching. Når jeg gjør dette: (let [[x y & r [1 2 3 4]]]) vil x få verdien 1, y får verdien 2, og r får resten av vectoren, altså [3 4] i dette tilfellet. Men om du følger nøye med så ser du at jeg nå plukker elementer fra starten av vectoren, og da ser det mer ut som en kø enn en stack. Trikset er at jeg reverserer stacken før jeg gjør dette, og da går det jo for det samme.

Selve utregningen, evalueringen av pluss, minus, gange og deling, benytter seg av det faktum at i Clojure er data det samme som kode, og kode er det samme som data. Jeg bygger rett og slett opp et Clojure-uttrykk, f.eks. (+ 1 2), som jeg så evaluerer og får resultatet 3.

Bloopers

Alle filmer inneholder feil, også denne. Blant annet implementerte jeg en måte å avslutte programmet på (ved at man sendte kommandoen quit), men jeg tok den aldri i bruk.

Den morsomste feilen er derimot metoden som ble hetende promp! Det skulle selvsagt ha vært prompt.


comments powered by Disqus