Filtrer, Projiser og Aggreger i Clojure
- Thursday, June 24th, 2010
- Skriv en kommentar
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.
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 #:
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#:
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…
Kategorier: LISP/Clojure, Polyglot.
RSS feed for kommentarene.
Tilbaketråkk.



June 25th, 2010 at 10:24 am
PS: Oppdaget akkurat at Clojure har en even-funskjon. Det betyr at (= (rem % 2) 0) kan erstattes med (even? %).
June 25th, 2010 at 10:34 am
Noe slikt du mener? (i scheme (noe jeg og er ganske nybegynner i))
(define (-> x . fs)
(define (chain x fs)
(if (null? fs) x (chain ((car fs) x) (cdr fs))))
(chain x fs))
(define (curry f . x) (lambda y (apply f (append x y))))
(define aList ‘(1 2 3 4 5 6 7 8 9 10))
(define (reduce f base lis)
(if (null? lis)
base
(f (car lis) (reduce f base (cdr lis)))))
(-> aList
(curry filter (lambda (x) (= (modulo x 2) 0)))
(curry map number->string)
(curry reduce (lambda (x y) (string-append x ” ” y)) “”)
display)
June 25th, 2010 at 10:47 am
Og da har jeg funnet ut hvordan partial application og function composition fungerer i Clojure også! Her er et eksempel hvor jeg hvor jeg bruker begge deler til å kombinere 3 anonyme funksjoner:
June 25th, 2010 at 10:53 am
Ameth, et tips hvis du vil skrive kode i kommentarene. Bruk <tt><pre>{koden din her}</pre></tt>
June 25th, 2010 at 11:03 am
Ah, takk.
(define (-> x . fs)
(define (chain x fs)
(if (null? fs) x (chain ((car fs) x) (cdr fs))))
(chain x fs))
(define (curry f . x) (lambda y (apply f (append x y))))
(define aList ‘(1 2 3 4 5 6 7 8 9 10))
(define (reduce f base lis)
(if (null? lis)
base
(f (car lis) (reduce f base (cdr lis)))))
(-> aList
(curry filter (lambda (x) (= (modulo x 2) 0)))
(curry map number->string)
(curry reduce (lambda (x y) (string-append x ” ” y)) “”)
display)
Jeg tror ikke scheme har partial, comp og reduce innebygget, så jeg lagde meg dem.
June 25th, 2010 at 11:04 am
Ser ikke ut som om det virket.
June 25th, 2010 at 11:14 am
Tydeligvis ikke. Kanskje det bare er jeg som får lov til det :) Uansett takk for kodeeksemplet, interresant!
June 25th, 2010 at 11:18 am
Bare hyggelig :)
June 28th, 2010 at 10:13 am
Nok en forbedring. Thread-makroen jeg brukte i eksempelet i denne blogposten (->) sender listen/resultatet videre som andre argument til neste metode i listen, og det skapte problemer. ->> sender derimot resultatet videre som siste argument, og da fungerte det bedre. Klarte nå å sette opp en pipeline med partial application uten å bruke “partial”:
Output fra denne lille Clojure-snutten er “2 8 54″. Clojure oppfører seg nå som forventet i forhold til det jeg har lært fra F#.., herlig å kunne overføre kunnskap fra et språk til et annet!