Filtrer, Projiser og Aggreger i Clojure


torsdag 24. juni 2010 Clojure

Siden jeg sitter og leker meg med Clojure tenkte jeg det kunne passe med en liten revamp av filtrer, projiser, aggreger blogposten fra et par uker tilbake. Her er de tilsvarende høyereordens-funksjonene i en kodesnutt som viser hvordan jeg kan løse den samme oppgaven i Clojure. Merk at LISP/Clojure kaller projiseringsfunskjonen for map (som de fleste andre språk forsåvidt), og aggregeringsfunksjonen heter reduce.

47 (defn fold-it [col]
48       "Concats all elements with separating whitespace"
49       (reduce #(str %1 %2 " ") "" col))
50
51 (defn string-it [col]
52       "Projects all elements to their string representation"
53       (map #(.toString %) col))
54
55 (defn even-it [col]
56       "Returns a new collection with only the even numbers"
57       (filter #(= (rem % 2) 0) col))
58
59 (def a-list [1 2 3 5 8 13 21 33 54]) ; actually a vector ;)
60
61 ; calling the three methods on the list
62 (do (println
63     (fold-it (string-it (even-it a-list)))))
64
65 ; using pipelining (the thread macro) for higher readability
66 (do (-> a-list
67         even-it
68         string-it
69         fold-it
70         println))

Uforklarlig? Til å begynne med ser LISP-syntax litt kryptisk ut ja. Men jeg kan i alle fall forsøke å forklare noe av det for deg. Bruken av #-tegnet er syntaktisk sukker for å lage en anonym funksjon, hvor %/%1, %2 etc. refererer til funksjonens parametre. Linje 57 kunne for eksempel vært skrevet på følgende måte uten å bruke #:

57       (filter (fn [n] (= (rem n 2) 0)) col))

Begynner du med den innerste parantesen ser du her et kall til funksjonen rem, som kalkulerer resten (reminder) når n deles på 2. En parantes lengre ut ser du et kall til =, med parametrene 0 og reminderen som nettopp ble kalkulert – altså sjekker man om reminder er lik 0. fn [n] betyr at vi deklarerer en anonym funksjon som tar en parameter vi kaller n. Helt ytterst kaller jeg filter-funksjonen som har to parametre: den anonyme funksjonen for å avgjøre om et tall er et partall, og kollekjonen av tall.

Dette er altså ikke er helt ulikt det man for eksempel ville ha skrevet i F#:

1     List.filter (fun n -> n % 2 = 0) col

Jeg forsøkte også å lage en variant av pipeliningen (linje 66 til 70) som bruker inline funksjoner og partial application, slik som jeg gjorde med F# helt til slutt i filtrer, projiser, aggreger, men det har jeg sålangt ikke fått til. Noen som kan Clojure eller LISP som kan bistå/kommentere? Får vel lese litt mer…


comments powered by Disqus