Filtrer, Projiser og Aggreger i 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…

Kategorier: LISP/Clojure, Polyglot.
RSS feed for kommentarene. Tilbaketråkk.

9 kommentarer til “Filtrer, Projiser og Aggreger i Clojure”

  1. Torbjørn Says:

    PS: Oppdaget akkurat at Clojure har en even-funskjon. Det betyr at (= (rem % 2) 0) kan erstattes med (even? %).

  2. Ameth Says:

    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)

  3. Torbjørn Says:

    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:

    (def produce-even-string
    		 (comp
    			 (partial reduce #(str %1 %2 " ") "")
    			 (partial map #(.toString %))
    			 (partial filter (fn [n] (= (rem n 2) 0)))))
    
    (println (produce-even-string a-list))
    

  4. Torbjørn Says:

    Ameth, et tips hvis du vil skrive kode i kommentarene. Bruk <tt><pre>{koden din her}</pre></tt>

  5. Ameth Says:

    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.

  6. Ameth Says:

    Ser ikke ut som om det virket.

  7. Torbjørn Says:

    Tydeligvis ikke. Kanskje det bare er jeg som får lov til det :) Uansett takk for kodeeksemplet, interresant!

  8. Ameth Says:

    Bare hyggelig :)

  9. Torbjørn Says:

    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”:

    (->> [1 2 3 5 8 13 21 33 54]
    		 (filter #(even? %))
    		 (map #(.toString %))
    		 (reduce #(str %1 %2 " ") "")
    		 println)
    

    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!

Skriv en kommentar

Tillatte tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>


Einar W. Høst: Det er jo læringen som gjør det morsomt! Se også http://norvig.com/21-days...

Pagliacci: OBS! tl;wr. Det er vel akuratt det jeg sliter med med min læring innenfor pr...

Torbjørn: La oss anta to ulike definisjoner av Template Method pattern - de to ytterpunkte...

Lars-Petter: Hei igjen. Siden du inviterer til å ta diskusjonen i bloggen, og har tatt deg t...

Torbjørn: Lars-Petter: Det er én måte å se det på. Det er absolutt fortsatt Template M...

Lars-Petter: Hei. Har du ikke i prinsippet her gått over fra Template Method (arv) til Strat...

Christian Abildsø: I alle fall i C#, så føles dette som kode som blir mer fleksibel men vanskelig...

Torbjørn: Hei Henrik, og takk for tilbudet. Ble oppmerksom på Rasberry Pi for under en uk...

Henrik Sandaker Palm: Ang. større hobby prosjekt. Du er som er en slik rakker på programmering har j...

Øivind Nilsen: Slutt å bruke mobilnummeret mitt som eksempel !...

Mulig relaterte linker

 Hold deg oppdatert

Søk i bloggen

Ferske innlegg

  • En historie om programmering
  • Template Method del 4: Multippel arv
  • Template Method Intermesso
  • Template Method del 3: Bare funksjoner
  • Kategorier

  • .net ninja (37)
  • Bøker (17)
  • Diverse prosjekter (35)
  • DSL (10)
  • Erlang (10)
  • F# (5)
  • Hardware (1)
  • Jobb (77)
  • Julekalender (51)
  • kjempekjekt.com (23)
  • LISP/Clojure (33)
  • NNUG / community (60)
  • O/RM & databaser (10)
  • Off topic (116)
  • OO-design/clean code (30)
  • Podcasts (14)
  • Polyglot (77)
  • Ruby (27)
  • Silverlight / RIA (3)
  • Software/verktøy (20)
  • Softwareutvikling (21)
  • Testing / TDD (30)
  • the contiki strip (13)
  • User experience (3)
  • WCF (3)
  • Webutvikling (32)
  • WPF (9)
  • WTF (12)
  • Last ned Wallpaper

    Programmeringsbloggens tøffe skrivebordsbakgrunn med snippets fra ulike språk laster du ned her!

    Abonner via epost

    Om du vil kan du få alle nye blogposter tilsendt til din epost. Abonner nå, det er kjempeenkelt!

    Meta