Practicumbundel bij Inleiding Functioneel Programmeren met Clojure
Auteurs: Michiel Borkent (2013)
Daan Vermanen en Martijn Bouma (eerste versie 2012)
Cursusjaar: 2012-2013
Terug naar de index-pagina
Inhoudsopgave
Tips en hulpmiddelen
- Hou je aan de Clojure style guide
- Op http://www.tryclj.com kun je Clojure-expressies in de browser uitproberen
- Clojure cheat sheet: http://clojure.org/cheatsheet
Bronnen die je kan gebruiken bij het practicum:
Week 1
- Op http://www.tryclj.com is een sandboxed Clojure REPL te vinden met een tutorial. Doorloop deze tutorial van begin tot eind. Wat is de uitkomst van de laatste expressie die je hebt moeten schrijven in deze tutorial?
- Installeer Leiningen, Eclipse en de CounterClockWise Plugin via de Eclipse Marketplace. Zie ook het gedeelte over Leiningen in het laatste hoofdstuk van het dictaat. Maak in Eclipse een nieuw Leiningen-project aan, waarin je je practicumuitwerkingen kan bewaren en runnen.
- De expressie
(/ 3 4)
geeft als uitkomst:3/4
. Dit is eenRational
, een datatype in Clojure welke een breuk voorstelt. Schrijf een expressie die0.75
geeft als uitkomst van3
gedeeld door4
. - Zet onderstaande rekenkundige expressie uit Java om naar Clojure:
int x = 465 - 774 * 3 / 3 + 774;
- Schrijf een do-expressie waarin de expressie van de vorige opgave wordt geprint als side-effect en het antwoord op de berekening teruggegeven wordt als resultaat van de expressie.
- Schrijf een if-expressie die twee data vergelijkt. Je kunt in
Clojure twee Java Dates aanmaken als volgt:
(def d1 (java.util.Date.)) (def d2 (java.util.Date.))
Hier wordt de constructor van
Date
aangeroepen zonder argumenten.Geef de String
"de data zijn gelijk"
terug indien de data gelijk zijn en anders"de data zijn ongelijk"
. -
- Bestudeer de documentatie van
cond
en beschrijf in je eigen woorden wat je ermee kan doen. - Zet onderstaande Java-code om naar Clojure-code. Gebruik hiervoor
cond
.if (1 == 2) { return "een is gelijk aan twee"; } else if (1 < 2) { return "een is kleiner dan twee"; } else { return "geen van beide"; }
- Wat moet er op de plek van de twee underscores komen te staan om
alle onderstaande expressies true te laten returnen. Gebruik in alle
drie de expressies dezelfde let-bindings.
(= 9 (let __ (+ x y))) (= 3 (let __ (+ y z))) (= 2 (let __ z))
- Bestudeer de documentatie van
- Deze opgave gaat over Java interop. Schrijf een functie die één
parameter genaamd "name" accepteert. Deze geeft de string "Hello,
<name>" terug. Door middel van Java-interop moet met de methode
replaceAll van de Java-klasse String <name> vervangen worden door
het argument dat is opgegeven. Je dient de volgende code aan te
vullen:
(defn hello [name] __ "Hello, <name>" __ ) (hello "world") ;;=> "hello world"
- Maak de 4Clojure-opgave http://www.4clojure.com/problem/36
Week 2
- Wat moet er op de underscores komen te staan in onderstaande
expressie om het getal 5 als resultaat op te leveren?
(__ max [1 2 5 3 4]) ;;=> 5
-
- Bestudeer onderstaande anonieme functiedefinities. De anonieme
functies worden meteen aangeroepen. Beredeneer de uitkomst van
onderstaande expressies, zonder dat je ze in de REPL evalueert.
((fn [x] (* x 3)) 4) (#(- % 7) 19) ((partial / 36) 3)
- Zet onderstaande functiedefinitie en aanroep van die functie om
in een anonieme functie die meteen wordt aangeroepen, zoals
hierboven.
(defn sqr [x] (* x x)) (sqr 5) ;;=> 25
- Herschrijf onderstaande expressie met de kortere notatie voor
anonieme functies (#).
(filter (fn [x] (< x 5)) [1 6 5 2 3])
- Bestudeer onderstaande anonieme functiedefinities. De anonieme
functies worden meteen aangeroepen. Beredeneer de uitkomst van
onderstaande expressies, zonder dat je ze in de REPL evalueert.
- Definieer een functie met de parameters
[x y & others]
. Wanneer alleen de parametersx
eny
aan de functie meegegeven worden, worden deze met elkaar vermenigvuldigd. Wanneer ookothers
meegegeven wordt, worden alle waarden bij elkaar opgeteld. -
- Definieer een functie die een lijst accepteert en het laatste
element teruggeeft. Het is verboden om de functie
last
te gebruiken. - Maak een functie die een variabel aantal argumenten accepteert.
Filter de argumenten van type
String
en geef die terug als resultaat. De volgende expressies geven het type van een argument terug:(type "hallo") ;;=> java.lang.String (type 1) ;;=> java.lang.Integer
- Herschrijf onderstaande Java-code in Clojure met gebruik van
loop
.for (int i = 0; i < 10; i += 2) { System.out.println(i); }
- Definieer een functie die een lijst accepteert en het laatste
element teruggeeft. Het is verboden om de functie
- In de eerste onderstaande expressie wordt de hogere orde functie
filter
gebruikt in combinatie met de predicaatfunctiezero?
(uitclojure.core
). Schrijf zelf een predicaatfunctienot-zero?
zodat de tweede expressie de lijst(1 2 3 4 5 6)
oplevert.(filter zero? [1 2 0 3 4 0 5 6]) ;;=> '(0 0) (filter not-zero? [1 2 0 3 4 0 5 6]) ;;=> '(1 2 3 4 5 6)
-
- Bekijk de documentatie van de functie
partial
en leg deze in eigen woorden uit. - Vul onderstaande expressie aan.
(= '(3 4 5) (map (partial __) [1 2 3]))
- Vul onderstaande expressie aan.
(= "first second third" (let [a _ b _] ((partial __ "first") a b)))
- Zoek de documentatie op van
comp
(van compose) en leg deze in eigen woorden uit. - Vul de
let
-bindings aan en zorg dat ze in alle drie de expressies hetzelfde zijn.(= 8 (let __ ((comp max *) x y))) (= -8/3 (let __ ((comp - /) x y))) (= -5 (let __ ((comp + - -) x y)))
- Bekijk de documentatie van de functie
-
- Bestudeer de documentatie van
complement
en leg deze in eigen woorden uit. - Gegeven de predicaatfunctie
ends-with?
welke controleert of eenString
eindigt op een karakter of substring, schrijf een predikaatfunctienot-ends-with?
en maak hierbij gebruik vancomplement
. De functieends-with?
maakt gebruik van Java-interop. Kennis hiervan is voor het oplossen van deze opgave niet nodig.(defn ends-with? [s c] (.endsWith s (str c))) (ends-with? "hallo" \o) ;;=> true (ends-with? "hallo" "o") ;;=> true (ends-with? "hallo" "f") ;;=> false
- Bestudeer de documentatie van
- Gebruik onderstaande functie om een predicaatfunctie genaamd
divisible-by-three?
te maken.(defn divisible [denom] (fn [num] (zero? (rem num denom))))
Bovenstaande functie kan op de volgende manier aangeroepen worden:
((divisible 3) 6) ;;=> true ((divisible 3) 5) ;;=> false
De predicaatfunctie moet als volgt aan te roepen zijn:
(divisible-by-three? 6) ;;=> true (divisible-by-three? 5) ;;=> false
- Schrijf een functie genaamd
max-except-first
die een lijst accepteert als argument. Door middel van destructuring wordt de eerste waarde van de lijst apart genomen en wordt de rest in een nieuwe lijst gezet. Daarvan moet de maximale waarde teruggeven worden. Verboden om te gebruiken:first
enrest
. Vergeet niet omapply
te gebruiken. Zorg ervoor dat onderstaande expressie hetzelfde antwoord geeft:(max-except-first [100 78 7 9 12]) ;;=> 78
- Lees het voorbeeld "Square roots by Newton's method" op http://mitpress.mit.edu/sicp/full-text/sicp/book/node12.html. Dit voorbeeld is afkomstig uit het boek SICP (Structure and Interpretation of Computer Programs) en geschreven in de taal Scheme, een ander Lisp-dialect. Vertaal dit voorbeeld naar Clojure en demonstreer dat je functie een benadering van de wortel van een getal kan geven. De oefeningen op de pagina zijn optioneel.
Week 3
- De volgende opgaven gaan over lists.
- Vul onderstaande expressies aan (de antwoorden hoeven in dit geval
niet hetzelfde te zijn):
(= (list __) '(:aap :noot :mies)) (= '(1 2 3 4) (flatten '(1 2 (__)))) (= '(1 2 3 4) (conj '(3 4) __))
- Schrijf een functie waarmee de inhoud van een lijst omgedraaid kan
worden. Het is verboden om
reverse
te gebruiken. - Maak de 4Clojure-opgave http://www.4clojure.com/problem/4
- Maak de 4Clojure-opgave http://www.4clojure.com/problem/5
- Vul onderstaande expressies aan (de antwoorden hoeven in dit geval
niet hetzelfde te zijn):
- De volgende opgaven gaan over vectoren.
- Vul onderstaande expressies aan.
(= '(1 2 3 4) (conj [1 2] __)) (= '(1 2 3 4) (into () __))
- Maak 4Clojure-opgave http://www.4clojure.com/problem/6
- Maak 4Clojure-opgave http://www.4clojure.com/problem/7
- Vul onderstaande expressies aan.
- De volgende opgaven gaan over maps.
- Creëer de volgende map door middel van
zipmap
:{:first 1, :second 2, :third 3}
(de volgorde in een map is niet van belang). - Creëer eenzelfde map door middel van
interleave
en de functiehash-map
. - Gegeven de volgende vector met maps:
(def tentamencijfers [{:naam "Piet" :cijfer 7} {:naam "Klaas" :cijfer 3}])
Gebruik assoc-in om de vector tentamencijfers terug te geven waarbij het cijfer van Klaas veranderd is in een 10.
- Voeg aan de expressie die je voor de vorige opgave hebt moeten schrijven de functie get-in toe om alleen het cijfer 10 terug te laten geven.
- Maak 4Clojure-opgave http://www.4clojure.com/problem/10
- Maak 4Clojure-opgave http://www.4clojure.com/problem/11
- Maak 4Clojure-opgave http://www.4clojure.com/problem/134
- Creëer de volgende map door middel van
- Opgaven over sets
- Schrijf een expressie die de elementen :a en :d uit de set #{:a :b :c :d} verwijdert.
- Schrijf een expressie die van de vector [1 1 2 3] een set maakt. Hoeveel elementen heeft de set en waarom?
- Schrijf een expressie die de sets #{1 2 3 4} #{5 6 7 8} tot één set samenvoegt.
- Maak de 4Clojure-opgave http://www.4clojure.com/problem/8
- Maak de 4Clojure-opgave http://www.4clojure.com/problem/9
- Maak de 4Clojure-opgave http://www.4clojure.com/problem/81
- Sequences
- Voeg het getal 5 toe aan de vector
[4 3 2 1]
. Zorg dat5
vooraan de vector wordt toegevoegd. - Maak van de vector
["een" "twee" "drie"]
de string"een twee drie"
. - Maak een functie
my-but-last
waarmee het een-na-laatste element wordt teruggegeven van een sequence. Het is verboden de functiebut-last
te gebruiken. Bijvoorbeeld(my-but-last ["maandag" "dinsdag" "woensdag" "donderdag" "vrijdag"])
levert"donderdag"
op. - Maak een functie waarmee dubbele waarden uit een sequence worden
gefilterd en die een sequence met unieke waarden oplevert. Het
is verboden om
distinct
te gebruiken.
- Voeg het getal 5 toe aan de vector
- Lazy sequences
- Zoek de documentatie van de functie
repeat
op. Schrijf een functie genaamdrepeat-fifteen
welke een lazy sequence oplevert waarin 15 keer een waarde die als parameter kan worden meegegeven wordt herhaald. - Zoek uit hoe onderstaande XML uitgelezen kan worden in Clojure.
Schrijf een functie die de namen en cijfers uit de onderstaande
XML in een map
teruggeeft:
{"Piet" 7, "Klaas" 10}
(je kan Strings als keys gebruiken). Gebruik bijvoorbeeld de functiexml-seq
die van XML een sequence kan maken. Hier is een goede uitleg ervan te vinden: http://www.gettingclojure.com/cookbook:xml-html(use 'clojure.xml) (def xmlstring "<?xml version=\"1.0\" encoding=\"utf-8\"?> <tentamenresultaten> <resultaat> <naam>Piet</naam> <cijfer>7</cijfer> </resultaat> <resultaat> <naam>Klaas</naam> <cijfer>10</cijfer> </resultaat> </tentamenresultaten>") (def xmlinputstream (java.io.ByteArrayInputStream. (.getBytes xmlstring))) (def parsed-xml (parse xmlinputstream))
- Gebruik de predicaatfunctie
divisible-by-three?
die je in week 2 hebt gemaakt in een for-expressie die een sequence oplevert van alleen maar getallen die deelbaar zijn door 3. Gebruik als invoer-sequence(range 20)
. - Herschrijf de vorige opgave en maak gebruik van
filter
. Wat is het verschil tussenfilter
enfor
?
- Zoek de documentatie van de functie
Week 4
- Recursie.
- A. Schrijf de volgende recursieve Java-methode om naar een
tail-recursive Clojure functie. Maak dus gebruik van
recur
.private static int sumTo(int n) { if (n == 0) return 0; return n + sumTo(n - 1); }
B. Maak een versie waarbij het gebruik van
recur
vervangen wordt door een hogere orde functie. - Schrijf een recursieve hogere orde functie
times-called
welke een reken-functiecalc-fn
accepteert, een startwaardestart-val
en een getallimit
. De hogere orde functie berekent hoe vaak de functie aangeroepen kan worden, totdat de limiet overschreden wordt. De functiecalc-fn
accepteert een getal en levert een getal op. Voorbeelden van aanroepen vantimes-called
:(times-called (fn [x] (+ x x)) 2 1000) ;;=> 8 (times-called (fn [x] (* x x)) 2 1000) ;;=> 3 (times-called (fn [x] (Math/pow x x)) 2 1000) ;;=> 2
- A. Schrijf de volgende recursieve Java-methode om naar een
tail-recursive Clojure functie. Maak dus gebruik van
- Atoms.
- Maak een atom die boeken kan opslaan in een vector. Boeken
bewaar je in een map:
{:type :book, :title "The Joy of Clojure", :authors "Fogus and Houser", :publisher "Manning"}
Schrijf daarbij functies genaamd
insert-book
,get-book
,update-book
endelete-book
om de boeken te kunnen beheren. Demonstreer duidelijk de werking van je programma door een scenario uit te programmeren waarin boeken worden geinsert, opgevraagd, geüpdated en verwijderd. Houdt rekening met o.a. de volgende zaken:- Een boek mag niet worden toegevoegd als er al een boek is met dezelfde titel
- Een boek mag alleen worden geüpdated als er al een boek is met dezelfde titel
- Schrijf twee functies:
start
enstop
welke de werking van een simpele stopwatch voorstellen. De twee functies moeten als volgt te gebruiken zijn:(start!) ;;=> "Stopwatch started" (start!) ;;=> "Stopwatch already started" (stop!) ;;=> "3.618 seconds passed since start" (stop!) ;;=> "Start stopwatch first" (start!) ;;=> "Stopwatch started" (stop!) ;;=> "105.838 seconds passed since start"
Gebruik een atom om de tijd bij te houden die verstreken is.
- Bestudeer de functie
memoize
op http://clojure.org/atoms. Wat is het verschil met voorbeeld van Fibonacci in het dictaat waarbij ook memoization is toegepast? Waarom is het niet nodig ommemoize
als macro te schrijven? Ismemoize
wel of geen hogere orde functie?
- Maak een atom die boeken kan opslaan in een vector. Boeken
bewaar je in een map:
- Macro's.
- Schrijf een macro genaamd
my-for
, een variatie opfor
, welke je als volgt kunt gebruiken:(my-for i 10 15 i (* i 2) (* i 3)) ;;=> ([10 20 30] [11 22 33] [12 24 36] [13 26 39] [14 28 42])
Het eerste argument van
my-for
is een naam van een iteratievariabele. Het tweede argument is een beginwaarde en het derde argument is de eindwaarde. De iteratievariabele doorloopt de waarden vanaf de beginwaarde tot de eindwaarde. Daarna volgen er expressies waarin aan de iteratievariabele gerefereerd kan worden. De waarden van de expressies worden verzameld in een lijst van vectoren, per iteratie een vector met de waarden van de expressies.Enkele voorbeelden:
(my-for c 97 123 (char c)) ;;=> ([\a] [\b] [\c] [\d] ... [\z]) (apply str (apply concat (my-for c 97 123 (char c)))) ;;=> "abcdefghijklmnopqrstuvwxyz"
- Breidt de macro
my-for
uit zodat het ook mogelijk wordt van hoog naar laag te itereren:(apply str (apply concat (my-for c 122 96 (char c)))) ;;=> "zyxwvutsrqponmlkjihgfedcba"
- Schrijf een macro genaamd
Week 5 en 6: webapplicatie
Ontwerp een klein spelletje als webapplicatie en maak hierbij gebruik van https://github.com/borkdude/tictactoe of gebruik een Luminus-template: http://www.luminusweb.net/.
Ideeën voor spelletjes:
- Niveau 'easy':
- Galgje
- Quiz
- Bingo
- Niveau 'medium':
- Schuifpuzzel
- Memory
- Niveau 'hard':
- Battleship
- Minesweeper
- Pesten (kaartspel)
of een eigen idee wat goedgekeurd wordt door de docent.
Gesuggereerde werkwijze
- Ontwerp eerst het model op een interactieve wijze (REPL) en schrijf
wanneer functies een voorlopige vorm hebben aangenomen er tests
voor in een corresponderende test namespace, zoals bij het tictactoe-spel.
Draai de tests regelmatig met
lein test
om te kijken of je model nog correct werkt. Gebruik zoveel mogelijk testbare functies (dus referentieel transparant) die je in het test-gedeelte test. In de tests neem je tevens een paar scenario's op die de hele flow van een spel illustreren en testen. - Ontwerp daarna de view op een interactieve manier. Deel de html-generatie op in stukjes die makkelijk zijn uit te proberen vanaf de REPL. Test daarna de gehele applicatie in de browser.
- Zet daarna de controller op (Compojure routes en bijbehorende besturingslogica).
- Start de browser via de
repl
namespace met de functiestart-server
. Los eventuele problemen op.
Op te leveren
- Github-link naar project (zodat de code makkelijk door de docent is in te zien)
- Deploy de applicatie eventueel naar Heroku (zie de README van https://github.com/borkdude/tictactoe en https://www.heroku.com/) zodat deze voor de docent makkelijk te testen is
- Documentje met uitleg over het spel, de spelregels, toelichting bij code waar nodig, verantwoording van keuzes, wat mag de docent vooral niet over het hoofd zien bij de beoordeling?
Beoordeling / eisen
- De applicatie werkt naar verwachting, bevat geen bugs, geen imperatieve programmeerstijl
- De kennis/vaardigheden die opgedaan zijn tijdens de les zijn duidelijk zichtbaar in de opgeleverde code
- Het model van de applicatie bevat waar mogelijk testbare functies (referentieel transparant)
- Testen voor het model in een aparte test-namespace, gebruikmakende van clojure.test. Zinvolle testgevallen.
- De view is opgedeeld in functies die makkelijk te 'testen' zijn vanaf de REPL
- Bonus: de applicatie ziet er mooi uit