diff --git a/project.clj b/project.clj index 7e630dd..c6272f4 100644 --- a/project.clj +++ b/project.clj @@ -7,11 +7,8 @@ :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :min-lein-version "2.8.1" - :implicits false - :dependencies [[org.clojure/clojure "1.10.0"] [org.clojure/core.async "0.4.490"] - [org.clojure/test.check "0.9.0"] [integrant "0.7.0"] @@ -30,4 +27,5 @@ [org.apache.lucene/lucene-queryparser ~lucene-version] [org.apache.lucene/lucene-analyzers-common ~lucene-version]] - :profiles {:dev {:dependencies [[integrant/repl "0.3.1"]]}}) + :profiles {:dev {:dependencies [[integrant/repl "0.3.1"]]}} + :aot [icw.core]) diff --git a/src/icw/async/core.clj b/src/icw/async/core.clj new file mode 100644 index 0000000..fe3f38b --- /dev/null +++ b/src/icw/async/core.clj @@ -0,0 +1,32 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/async/core.org::*Preamble%20Code][Preamble Code:1]] +(ns icw.async.core + (:require [clojure.core.async :as a + :refer [chan go go-loop ! take! put!]] + [icw.async.rlsc :as rlsc] + [icw.data.gen :as data-gen])) + +(defonce counter (atom 0)) +(def observing-mapper (map (fn [e] + (swap! counter inc) + e))) + +(def in-ch (a/chan (a/dropping-buffer 32) observing-mapper)) + +(defonce enabled? (atom false)) +(defonce quit? (atom false)) + +(defn enable-stream! [] + (reset! enabled? true)) +(defn disable-stream! [] + (reset! enabled? false)) + +(defonce generator-loop + (go-loop [stream (data-gen/get-albums-xs)] + ; FIXME + ; (a/! in-ch (first stream))) + (recur (rest stream)))))) +;; Preamble Code:1 ends here diff --git a/src/icw/async/intro.clj b/src/icw/async/intro.clj new file mode 100644 index 0000000..3e03d4a --- /dev/null +++ b/src/icw/async/intro.clj @@ -0,0 +1,101 @@ +(ns icw.async.chapter1 + (:require [clojure.core.async :refer + [go chan ! >!! chan ----> Process 2 + +;; In context of `go`, put is `>!` and take is `! c 1)) + + + ;; process #2 + ;; get something from channel + (go (pprint "Hello from process #2 " (!! c {:data "hello world"}) + (pprint "Fetching data from Mongodb completed")) + + ;; process #2 + (go (pprint "Data from mongodb #2 " (!! c1 {:data (str "hello world from process 1 time " time)}))) + + ;; process #2 + (future (let [time (rand-int 1000)] + (Thread/sleep time) + (>!! c2 {:data (str "hello world from process 2 time " time)}))) + + ;; process #3 how do we get output of the fastest result + (pprint (!! c1 {:data (str "hello world from process 1 time " time)}) + (pprint "released process 1!"))) + + ;; process #2 + (future (let [time (rand-int 1000)] + (Thread/sleep time) + (>!! c2 {:data (str "hello world from process 2 time " time)}) + (pprint "released process 2!"))) + + ;; process #3 + (let [[v c] (alts!! [c1 c2 t])] + (pprint v)))) + + + + +;; Exercise - 1 +;; Modify alts! example. If both requests take more than 300msec print +;; "Wubalubadubdub" and close both request channels +;; Hint - timeout generates a channel which will close after given time +;; On closing the channel it gets nil as the final value diff --git a/src/icw/async/intro_part2.clj b/src/icw/async/intro_part2.clj new file mode 100644 index 0000000..3b6b08f --- /dev/null +++ b/src/icw/async/intro_part2.clj @@ -0,0 +1,170 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Preamble][Preamble:1]] +(ns icw.async.intro-part2 + (:require [icw.common :refer :all] + [clojure.core.async :as a + :refer [go go-loop chan close! + ! !! take! put! + alts! alt! thread + buffer sliding-buffer dropping-buffer]])) +;; Preamble:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*chan][chan:1]] +(chan) +;; chan:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Put,%20and%20then%20take.][Put, and then take.:1]] +; A simple put, followed by a take +(let [ch (chan)] + (put! ch "hello, orderly world!") + (take! ch pprint)) +;; Put, and then take.:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Take,%20and%20put?][Take, and put?:1]] +(let [ch (chan)] + + ;; take!, and put!, are non-blocking operations + (take! ch pprint) + + ; The above returned immediately, + ; event though there was nothing to consume + (pprint "Now, we put!") + (put! ch "hello, world!") + + (take! ch pprint) + (put! ch "hello, again, world!")) +;; Take, and put?:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Pushing%20the%20limits%20-%20put%20and%20take][Pushing the limits - put and take:1]] +; How many puts can a channel take at once? +(let [num-msgs 1025 + ch (chan)] + (doseq [i (range num-msgs)] + (put! ch i)) + + (doseq [i (range num-msgs)] + (take! ch identity))) +;; Pushing the limits - put and take:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Add%20a%20cushion][Add a cushion:1]] +(let [chan-buf-sz 32 + put-limit 1025 + take-limit 64 + ;; Try using any of buffer, dropping-buffer and sliding-buffer + ch (chan (buffer chan-buf-sz))] + + (doseq [i (range put-limit)] + (put! ch i)) + + (doseq [i (range take-limit)] + (take! ch pprint))) +;; Add a cushion:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Let's%20go!][Let's go!:1]] +(let [c (chan)] + + (go (pprint "We have a new message: " (! c "Did you have to wait too long?"))) +;; Let's go!:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Going,%20going,%20go-loop!][Going, going, go-loop!:1]] +; Let's create a looping go process. +(defonce looping-ch-stop? (atom false)) +(def looping-ch + (let [ch (chan)] + (go-loop [msg ( 1 are ready? + ; Note that the out channel is very likely ready for action, + ; since we control when the action happens. + (condp = port + ch1 (pprint "We had an input '" val "' on ch1") + ch2 (pprint "We had an input '" val "' on ch2") + out-ch (pprint "Nothing came in. So, we sent out on out-ch")))) + + (if (zero? (rand-int 2)) + (put! ch1 "Hello, ch1") + (put! ch2 "Hello, ch2"))) +;; What's async without multiple actors?:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*And,%20what's%20clojure%20without%20convenience?][And, what's clojure without convenience?:1]] +(let [ch1 (chan) + ch2 (chan) + out-ch (chan 1) + priority? false] ;; Try variations + + (go (alt! + ch1 ([val] (pprint "We had an input \"" val "\" on ch1")) + ch2 ([val] (pprint "We had an input \"" val "\" on ch2")) + ;; Caution - It's a vector of vectors below. + [[out-ch "Out!"]] ([_] (pprint "Nothing came in. So, we sent out on out-ch")) + :priority priority?)) + (put! ch1 "Hello, ch1")) +;; And, what's clojure without convenience?:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/intro.org::*Where%20do%20the%20async%20'processes'%20run?][Where do the async 'processes' run?:1]] +(thread (pprint "Hello")) + +(go (pprint "Hello")) + +(let [c (chan)] + (go (pprint "In value: " (! c "HereIsAChannelMessage"))) + +(let [c (chan 8)] + (put! c "Hello, put!" + (fn [& args] + (pprint "On put! callback")) + false) + (take! c (fn [& [args]] (pprint "On take! callback" args)) false)) + + +(let [put-take-counter (atom 0) + c (chan (sliding-buffer 128)) + count++ (fn [] (swap! put-take-counter inc))] + + (def on-my-thread true) + (def on-core-async-thread false) + + (defn put-cb [v] + (pprint "put! = " v)) + (defn take-cb [x] + (pprint "take! = " x)) + (defn put-counter! [on-thread?] + (put! c (count++) put-cb on-thread?)) + (defn take-counter! [on-thread?] + (take! c take-cb on-thread?)) + + (comment + ;; Mix the put-s and take-s. Letting one run ahead of the other. + ;; Observe where (the thread) the prints happen. + (put-counter! on-core-async-thread) + (take-counter! on-core-async-thread) + (take-counter! on-my-thread) + (doseq [_ (range 1024)] + (put-counter! on-core-async-thread)) + (doseq [_ (range 128)] + (take-counter! on-my-thread)))) +;; Where do the async 'processes' run?:1 ends here diff --git a/src/icw/async/rlsc.clj b/src/icw/async/rlsc.clj new file mode 100644 index 0000000..b7a5d6d --- /dev/null +++ b/src/icw/async/rlsc.clj @@ -0,0 +1,108 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*The%20Template][The Template:1]] +(ns icw.async.rlsc + (:require [clojure.core.async :as a + :refer [go go-loop + chan close! timeout thread + ! take! put! >!! BoundedCounter (atom ulimit) (atom (or initial 0)))) + +(comment + ; Let's test our bounded counter. + ; Increment-tests first. Try modifying the limits + (let [c (new-counter 10 5)] + (doseq [_ (range 7)] + (println "Current count: " (counter c)) + (println "++ " (counter++ c)))) + + ; Decrement tests + (let [c (new-counter 10 13)] + (doseq [_ (range 7)] + (println "Current count: " (counter c)) + (println "-- " (counter-- c))))) +;; The Template:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*The%20API][The API:1]] +(defprotocol RLSCController + (start! [this]) + (modify-burst! [this new-burst-count]) + (zero! [this]) + (shutdown! [this])) +;; The API:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*Outline][Outline:1]] +(defrecord RLSC + [in-ch out-ch process-fn time-gap-ms burst-count] + ;; Any internal state to track?? + + RLSCController + ; We need two go processes + ; 1. One that tracks consumption and burst-rate limits + ; 2. The other processes messages per the policy + (start! [_]) + + ; Policy change at run-time + ; This needs to be conveyed to the go-blocks + ; which help us conform to policy + (modify-burst! [this new-burst-count]) + + ; Stop all transformation + ; Signal the go-block logic to clamp down on transformations. + (zero! [this]) + + ; Stop the go blocks. + ; How do we communicate with the go-blocks started in another place? + (shutdown! [this])) + +(defn new-rate-limiter [in-ch out-ch process-fn time-gap-ms burst-count] + (->RLSC in-ch out-ch process-fn time-gap-ms + (atom burst-count))) +;; Outline:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/async/rlsc.org::*Test%20runs][Test runs:1]] +(comment + (let [count (atom 0)] + (defn work-counter [] @count) + (defn work-counter++ [] (swap! count inc))) + + (def in-ch (chan (dropping-buffer 8))) + + (def out-ch (chan (dropping-buffer 8) + (map pprint) + (constantly nil))) + + (defn process-value [v] + (assoc v :heavy? true)) + (def r (new-rate-limiter in-ch out-ch + (fn [v] + (assoc v :heavy? true)) + 1000 3)) + (start! r) + + (zero! r) + (doseq [_ (range 5)] + (put! in-ch {:payload (work-counter++)}))) +;; Test runs:1 ends here diff --git a/src/icw/common.clj b/src/icw/common.clj new file mode 100644 index 0000000..ed1c9ec --- /dev/null +++ b/src/icw/common.clj @@ -0,0 +1,15 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/common.org::*Common%20utils][Common utils:1]] +(ns icw.common + (:require [clojure.pprint :as pp] + [clojure.datafy :refer [datafy]])) + +(defn thread-name [] + (-> (Thread/currentThread) + .getName)) + +(let [pp-lock (Object.)] + (defn pprint + [& s] + (locking pp-lock + (pp/pprint (apply str "[" (thread-name) "] " s))))) +;; Common utils:1 ends here diff --git a/src/icw/core.clj b/src/icw/core.clj new file mode 100644 index 0000000..1a8ac64 --- /dev/null +++ b/src/icw/core.clj @@ -0,0 +1,99 @@ +(ns icw.core + (:require [icw.system :as system] + [icw.data.process] + [icw.data.process-faster] + ;; [icw.java-interop.intro] + ;; [icw.search.reader] + [icw.async.intro] + [icw.async.rlsc] + [icw.web.core :as web]) + (:gen-class)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Introduction +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Start HTTP server + +(comment + (system/start!)) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Chapter 1 - Lazy sequences +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; http://localhost:6789/albums + +;; Where is it located ? Let's jump there +web/app + +icw.data.process/populate-db + +;; git checkout solutions src/ + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Chapter 2 - Concurrency +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Can we make make populate-db faster? + +'icw.data.process-faster + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Chapter 3 - Java interop +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; 'icw.java-interop.intro + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Chapter 4 - Java interop (search) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Go to +;; http://localhost:6789/search/beatles + +;; Let's go back to the routes +web/app + +;; We need fix search +;; icw.search.reader/search + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Chapter 5 - core.async introduction +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Let's write some core.async + +'icw.async.intro + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Chapter 6 - core.async exercise +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +'icw.async.rlsc + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Summary +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; How to choose libraries and build projects ??? + +;; Start from smallest unit of data +;; Build transformations based on your case +;; Deal with constraints +;; Bring in databases and dependencies at very last + + +;; When choosing libraries +;; It should work with immutable data structure +;; It should produce immutable data structure +;; It should produce lazy sequences when it can +;; Prefer libraries over frameworks +;; It shouldn't use dynamic vars + +;; Do concurrency when you actually have to +;; Adding concurrency primitives does add complexity to the system +;; When in doubt go for the simplest construct diff --git a/src/icw/data.clj b/src/icw/data.clj new file mode 100644 index 0000000..eca3621 --- /dev/null +++ b/src/icw/data.clj @@ -0,0 +1,24 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/data.org::*All%20Data][All Data:1]] +(ns icw.data + (:require [clojure.data.csv :as csv] + [clojure.java.io :as io] + [clojure.string :as string])) + +(defonce album-csv-file "data/albumlist.csv") + +(defn load-album-csv-file [album-csv-file] + (let [data (-> album-csv-file + io/resource + io/as-file + slurp) + csv (csv/read-csv data) + header (first csv) + header (map string/lower-case header) + data (rest csv) + processed (map zipmap + (->> header + (map keyword) + repeat) + data)] + processed)) +;; All Data:1 ends here diff --git a/src/icw/data/gen.clj b/src/icw/data/gen.clj new file mode 100644 index 0000000..3524a8d --- /dev/null +++ b/src/icw/data/gen.clj @@ -0,0 +1,38 @@ +(ns icw.data.gen + (:require [clojure.test.check.generators :as gen] + [clojure.string :as cs])) + +(def artist-gen (gen/elements ["The Beatles" "Bob Dylan" "The Rolling Stones" "Bruce Springsteen" "The Who" "Radiohead" "David Bowie" "Various Artists" "Elton John" "Led Zeppelin" "U2" "The Velvet Underground" "The Byrds" "Talking Heads" "Stevie Wonder" "Sly & The Family Stone" "The Smiths" "Pink Floyd" "The Police" "Ray Charles" "Al Green" "Muddy Waters" "Grateful Dead" "The Beach Boys" "The Doors" "Nirvana" "Steely Dan" "Elvis Presley" "Simon & Garfunkel" "The Clash" "Prince" "Miles Davis" "Bob Marley & The Wailers" "Kanye West" "Big Star" "Tom Waits" "Otis Redding" "James Brown" "R.E.M." "Cream" "Michael Jackson" "The Kinks" "The Jimi Hendrix Experience" "Jackson Browne" "Madonna" "Randy Newman" "Marvin Gaye" "Jay Z" "Black Sabbath" "Neil Young" "B.B. King" "Joni Mitchell" "Santana" "Public Enemy" "Johnny Cash" "Fleetwood Mac" "Creedence Clearwater Revival" "Aerosmith" "The Wailers" "Mott the Hoople" "PJ Harvey" "AC/DC" "Metallica" "The White Stripes" "Funkadelic" "Paul Simon" "The Notorious B.I.G." "Frank Sinatra" "Howlin' Wolf" "Ramones" "Run D.M.C." "Van Morrison" "The Band" "The Mothers of Invention" "Eagles" "MC5" "Neil Young & Crazy Horse" "Pixies" "Sam Cooke" "KISS" "Roxy Music" "Green Day"])) + +(def album-name-gen (gen/elements ["Sgt." "Pepper's" "Lonely" "Hearts" "Club" "Band" "Pet" "Sounds" "Revolver" "Highway" "61" "Revisited" "Rubber" "Soul" "What's" "Going" "On" "Exile" "on" "Main" "St." "London" "Calling" "Blonde" "on" "Blonde" "The" "Beatles" "(\"The" "White" "Album\")" "The" "Sun" "Sessions" "Kind" "of" "Blue" "The" "Velvet" "Underground" "&" "Nico" "Abbey" "Road" "Are" "You" "Experienced" "Blood" "on" "the" "Tracks" "Nevermind" "Born" "to" "Run" "Astral" "Weeks" "Thriller" "The" "Great" "Twenty_Eight" "The" "Complete" "Recordings" "John" "Lennon/Plastic" "Ono" "Band" "Innervisions" "Live" "at" "the" "Apollo," "1962" "Rumours" "The" "Joshua" "Tree" "Who's" "Next" "Led" "Zeppelin" "Blue" "Bringing" "It" "All" "Back" "Home" "Let" "It" "Bleed" "Ramones" "Music" "From" "Big" "Pink" "The" "Rise" "and" "Fall" "of" "Ziggy" "Stardust" "and" "the" "Spiders" "From" "Mars" "Tapestry" "Hotel" "California" "The" "Anthology" "Please" "Please" "Me" "Forever" "Changes" "Never" "Mind" "the" "Bollocks" "Here's" "the" "Sex" "Pistols" "The" "Doors" "The" "Dark" "Side" "of" "the" "Moon" "Horses" "The" "Band" "(\"The" "Brown" "Album\")" "Legend:" "The" "Best" "of" "Bob" "Marley" "and" "The" "Wailers" "A" "Love" "Supreme" "It" "Takes" "a" "Nation" "of" "Millions" "to" "Hold" "Us" "Back" "At" "Fillmore" "East" "Here's" "Little" "Richard" "Bridge" "Over" "Troubled" "Water" "Greatest" "Hits" "Meet" "The" "Beatles!" "The" "Birth" "of" "Soul" "Electric" "Ladyland" "Elvis" "Presley" "Songs" "in" "the" "Key" "of" "Life" "Beggars" "Banquet" "Chronicle:" "The" "20" "Greatest" "Hits" "Trout" "Mask" "Replica" "Greatest" "Hits" "Appetite" "for" "Destruction" "Achtung" "Baby" "Sticky" "Fingers" "Back" "to" "Mono" "(1958-1969)" "Moondance" "Kid" "A" "Off" "the" "Wall" "[Led" "Zeppelin" "IV]" "The" "Stranger" "Graceland" "Superfly" "Physical" "Graffiti" "After" "the" "Gold" "Rush" "Star" "Time" "Purple" "Rain" "Back" "in" "Black" "Otis" "Blue:" "Otis" "Redding" "Sings" "Soul" "Led" "Zeppelin" "II" "Imagine" "The" "Clash" "Harvest" "Axis:" "Bold" "as" "Love" "I" "Never" "Loved" "a" "Man" "the" "Way" "I" "Love" "You" "Lady" "Soul" "Born" "in" "the" "U.S.A." "The" "Wall" "At" "Folsom" "Prison" "Dusty" "in" "Memphis" "Talking" "Book" "Goodbye" "Yellow" "Brick" "Road" "20" "Golden" "Greats" "Sign" "\"Peace\"" "the" "Times" "40" "Greatest" "Hits" "Bitches" "Brew" "Tommy" "The" "Freewheelin'" "Bob" "Dylan" "This" "Year's" "Model" "There's" "a" "Riot" "Goin'" "On" "Odessey" "and" "Oracle" "In" "the" "Wee" "Small" "Hours" "Fresh" "Cream" "Giant" "Steps" "Sweet" "Baby" "James" "Modern" "Sounds" "in" "Country" "and" "Western" "Music" "Rocket" "to" "Russia" "Portrait" "of" "a" "Legend" "1951-1964" "Hunky" "Dory" "Aftermath" "Loaded" "The" "Bends" "If" "You" "Can" "Believe" "Your" "Eyes" "and" "Ears" "Court" "and" "Spark" "Disraeli" "Gears" "The" "Who" "Sell" "Out" "Out" "of" "Our" "Heads" "Layla" "and" "Other" "Assorted" "Love" "Songs" "Late" "Registration" "At" "Last!" "Sweetheart" "of" "the" "Rodeo" "Stand!" "The" "Harder" "They" "Come" "Raising" "Hell" "Moby" "Grape" "Pearl" "Catch" "a" "Fire" "Younger" "Than" "Yesterday" "Raw" "Power" "Remain" "in" "Light" "Marquee" "Moon" "Paranoid" "Saturday" "Night" "Fever:" "The" "Original" "Movie" "Sound" "Track" "The" "Wild," "the" "Innocent" "&" "the" "E" "Street" "Shuffle" "Ready" "to" "Die" "Slanted" "and" "Enchanted" "Greatest" "Hits" "Tim" "The" "Chronic" "Rejuvenation" "Parallel" "Lines" "Live" "at" "the" "Regal" "A" "Christmas" "Gift" "for" "You" "From" "Phil" "Spector" "Gris-Gris" "Straight" "Outta" "Compton" "Aja" "Surrealistic" "Pillow" "Deja" "vu" "Houses" "of" "the" "Holy" "Santana" "Darkness" "on" "the" "Edge" "of" "Town" "Funeral" "The" "B" "52's" "/" "Play" "Loud" "The" "Low" "End" "Theory" "Moanin'" "in" "the" "Moonlight" "Pretenders" "Paul's" "Boutique" "Closer" "Captain" "Fantastic" "and" "the" "Brown" "Dirt" "Cowboy" "Alive!" "Electric" "Warrior" "The" "Dock" "of" "the" "Bay" "OK" "Computer" "1999" "The" "Very" "Best" "of" "Linda" "Ronstadt" "Let's" "Get" "It" "On" "Imperial" "Bedroom" "Master" "of" "Puppets" "My" "Aim" "Is" "True" "Exodus" "Live" "at" "Leeds" "The" "Notorious" "Byrd" "Brothers" "Every" "Picture" "Tells" "a" "Story" "Something/Anything?" "Desire" "Close" "to" "You" "Rocks" "One" "Nation" "Under" "a" "Groove" "The" "Anthology:" "1961-1977" "The" "Definitive" "Collection" "The" "Rolling" "Stones," "Now!" "Natty" "Dread" "Fleetwood" "Mac" "Red" "Headed" "Stranger" "The" "Immaculate" "Collection" "The" "Stooges" "Fresh" "So" "Buffalo" "Springfield" "Again" "Happy" "Trails" "From" "Elvis" "in" "Memphis" "Fun" "House" "The" "Gilded" "Palace" "of" "Sin" "Dookie" "Transformer" "Blues" "Breakers" "With" "Eric" "Clapton" "(\"The" "Beano" "Album\")" "Nuggets:" "Original" "Artyfacts" "From" "the" "First" "Psychedelic" "Era," "1965-1968" "Murmur" "The" "Best" "of" "Little" "Walter" "Is" "This" "It" "Highway" "to" "Hell" "The" "Downward" "Spiral" "Parsley," "Sage," "Rosemary" "and" "Thyme" "Bad" "Modern" "Times" "Wheels" "of" "Fire" "Dirty" "Mind" "Abraxas" "Tea" "for" "the" "Tillerman" "Ten" "Everybody" "Knows" "This" "Is" "Nowhere" "Wish" "You" "Were" "Here" "Crooked" "Rain" "Crooked" "Rain" "Tattoo" "You" "Proud" "Mary:" "The" "Best" "of" "Ike" "and" "Tina" "Turner" "New" "York" "Dolls" "Bo" "Diddley" "/" "Go" "Bo" "Diddley" "Two" "Steps" "From" "the" "Blues" "The" "Queen" "Is" "Dead" "Licensed" "to" "Ill" "Look-Ka" "Py" "Py" "Loveless" "New" "Orleans" "Piano" "War" "The" "Neil" "Diamond" "Collection" "American" "Idiot" "Nebraska" "Doolittle" "Paid" "in" "Full" "Toys" "in" "the" "Attic" "Nick" "of" "Time" "A" "Night" "at" "the" "Opera" "The" "Kink" "Kronikles" "Mr." "Tambourine" "Man" "Bookends" "The" "Ultimate" "Collection" "Mr." "Excitement!" "My" "Generation" "Howlin'" "Wolf" "Like" "a" "Prayer" "Can't" "Buy" "a" "Thrill" "Let" "It" "Be" "Run" "D.M.C." "Black" "Sabbath" "The" "Marshall" "Mathers" "LP" "All" "Killer" "No" "Filler!" "The" "Jerry" "Lee" "Lewis" "Anthology" "Freak" "Out!" "Live/Dead" "The" "Shape" "of" "Jazz" "to" "Come" "Automatic" "for" "the" "People" "Reasonable" "Doubt" "Low" "The" "Blueprint" "The" "River" "Complete" "&" "Unbelievable:" "The" "Otis" "Redding" "Dictionary" "of" "Soul" "Metallica" "(\"The" "Black" "Album\")" "Trans" "Europa" "Express" "Whitney" "Houston" "The" "Kinks" "Are" "The" "Village" "Green" "Preservation" "Society" "The" "Velvet" "Rope" "Stardust" "American" "Beauty" "Crosby," "Stills" "&" "Nash" "Tracy" "Chapman" "Workingman's" "Dead" "The" "Genius" "of" "Ray" "Charles" "Child" "Is" "Father" "to" "the" "Man" "Quadrophenia" "Paul" "Simon" "Psychocandy" "Some" "Girls" "The" "Beach" "Boys" "Today!" "Dig" "Me" "Out" "Going" "to" "a" "Go-Go" "Nightbirds" "The" "Slim" "Shady" "LP" "Mothership" "Connection" "Rhythm" "Nation" "1814" "Anthology" "of" "American" "Folk" "Music" "Aladdin" "Sane" "All" "That" "You" "Can't" "Leave" "Behind" "My" "Life" "Folk" "Singer" "Can't" "Get" "Enough" "The" "Cars" "Music" "of" "My" "Mind" "I'm" "Still" "in" "Love" "With" "You" "Los" "Angeles" "Anthem" "of" "the" "Sun" "Something" "Else" "by" "The" "Kinks" "Call" "Me" "Talking" "Heads:" "77" "The" "Basement" "Tapes" "White" "Light/White" "Heat" "Kick" "Out" "the" "Jams" "Songs" "of" "Love" "and" "Hate" "Meat" "Is" "Murder" "We're" "Only" "in" "It" "for" "the" "Money" "The" "College" "Dropout" "Weezer" "(Blue" "Album)" "Master" "of" "Reality" "Coat" "of" "Many" "Colors" "Fear" "of" "a" "Black" "Planet" "John" "Wesley" "Harding" "Grace" "Car" "Wheels" "on" "a" "Gravel" "Road" "Odelay" "A" "Hard" "Day's" "Night" "Songs" "for" "Swingin'" "Lovers!" "Willy" "and" "the" "Poor" "Boys" "Blood" "Sugar" "Sex" "Magik" "The" "Sun" "Records" "Collection" "Nothing's" "Shocking" "MTV" "Unplugged" "in" "New" "York" "The" "Miseducation" "of" "Lauryn" "Hill" "Damn" "the" "Torpedoes" "The" "Velvet" "Underground" "Surfer" "Rosa" "Back" "Stabbers" "Burnin'" "Amnesiac" "Pink" "Moon"])) + +(def genre-gen (gen/elements ["Electronic" "Ambient" "Ambient dub" "Dark ambient" "Drone music" "Space music" "Illbient" "Psybient" "Isolationism" "Asian Underground" "Breakbeat" "Acid breaks" "Baltimore club" "Big beat" "Broken beat" "Florida breaks" "Nu-funk" "Miami bass" "Jersey club" "Nu skool breaks" "Disco" "Afro / Cosmic disco" "Disco polo" "Euro disco" "Italo disco" "Nu-disco" "Space disco" "Downtempo" "Acid jazz" "Chill-out" "New-age music" "Space music" "Trip hop" "Drum and bass" "Liquid funk" "Neurofunk" "Neurohop" "Jump-up" "Darkstep" "Drumstep" "Extreme Ambient" "Funkstep" "Hardstep" "EDM (electronic dance music)" "Sambass" "Techstep" "Dub" "Ambient dub" "Dancehall" "Dub poetry" "Dub reggae" "Dub techno" "Dubstep" "Dubtronica" "Electro music" "Electro Swing" "Freestyle music" "Electroacoustic music" "Acousmatic music" "Musique concrète" "Electronic rock" "Alternative dance" "Indietronica" "Coldwave" "Dance-punk" "Dark wave" "Electroclash" "Electronicore" "Electropunk" "Ethereal wave" "Krautrock" "Minimal wave" "New rave" "Nu-gaze" "Space rock" "Synthpop" "Electronica" "Berlin School" "Dubtronica" "Ethnic electronica" "Folktronica" "Funktronica" "Laptronica" "Livetronica" "Trap" "Future bass" "Hardcore" "Gabba" "4-beat" "Breakbeat hardcore" "Bouncy techno" "Breakcore" "Hardbass" "Russian Hardbass" "Digital hardcore" "Darkcore" "Happy hardcore" "Mákina" "Frenchcore" "Speedcore" "Terrorcore" "UK hardcore" "Uptempo" "Doomcore" "Hardstyle" "Dubstyle" "Jumpstyle" "Lento violento" "Hi-NRG" "Eurobeat" "Eurodance" "Bubblegum dance" "Italo dance" "House music" "Acid house" "Ambient house" "Balearic beat" "Chicago house" "Deep house" "Future house" "Bass house" "G house" "Tropical house" "Diva house" "Electro house" "Big room house" "Complextro" "Fidget house" "Dutch house" "Moombahton" "Moombahcore" "French house" "Funky house" "Garage house" "Ghetto house" "Ghettotech" "Hardbag" "Hard house" "Car music" "Hard dance" "Hard NRG" "Hip house" "Italo house" "Jazz house" "Kidandali" "Kwaito" "Latin house" "Microhouse/Minimal house" "New beat" "Outsider house" "Progressive house" "Rara tech" "Tech house" "Tribal house" "Trival" "Witch house" "Industrial music" "Aggrotech" "Cybergrind" "Electro-industrial" "Dark electro" "Electronic body music" "Futurepop" "Industrial metal" "Industrial rock" "Japanoise" "Neue Deutsche Härte" "Power electronics" "Death industrial" "Power noise" "IDM" "Glitch" "Glitch Hop" "Wonky" "Jungle" "Darkcore jungle" "Raggacore" "Post-disco" "Boogie" "Electropop" "Chillwave" "Vaporwave" "Hardvapour" "Dance-pop" "Dance-rock" "Techno" "Acid techno" "Detroit techno" "Dub techno" "Free tekno" "Minimal techno" "Nortec" "Tecno brega" "Techdombe" "Trance music" "Acid trance" "Balearic trance" "Dream trance" "Euro-trance" "Hard trance" "Nitzhonot" "Psychedelic trance" "Full on" "Suomisaundi" "Goa trance" "Progressive trance" "Tech trance" "Uplifting trance" "Vocal trance" "Chill" "Chillstep" "UK garage" "2-step garage" "Dubstep" "Deathstep" "Brostep" "Chillstep" "Drumstep" "Reggaestep" "Trapstep" "Metalstep" "Club" "Breakstep" "Future garage" "Grime" "Grindie" "Speed garage" "Bassline/4x4 garage" "UK funky" "Nightcore" "Nightstep" "Video game music" "Chiptune" "Bitpop" "Game Boy music" "Shiny" "Skweee" "Nintendocore" "Tetris music" "American folk revival" "Anti-folk" "British folk revival" "Celtic music" "Chalga" "Contemporary folk" "Filk music" "Folk rock" "Folktronica" "Freak folk" "Indie folk" "Industrial folk" "Neofolk" "Progressive folk" "Protest song" "Psychedelic folk" "Singer-songwriter movement" "Skiffle" "Sung poetry" "Cowboy/Western music" "Alternative hip hop" "Australian hip hop" "Bongo Flava" "Boom bap" "British hip hop" "Chap hop" "Chopper rap" "Christian hip hop" "Cloud rap" "Conscious hip hop" "Crunk" "Crunkcore" "Drill" "Electro music" "Emo hip hop" "Experimental hip hop" "G-funk" "Ghetto house" "Ghettotech" "Golden age hip hop" "Grime" "Hardcore hip hop" "Hip house" "Hiplife" "Hip pop" "Hyphy" "Industrial hip hop" "Instrumental hip hop" "Jazz rap" "Jersey club" "Kwaito" "Lyrical hip hop" "Low Bap" "Merenrap" "Motswako" "Mumble rap" "Nerdcore" "New jack swing" "New school hip hop" "Old school hip hop" "Political hip hop" "Ragga" "Reggaeton" "Snap" "Jerkin'" "Trap" "Urban Pasifika" "Emo rap" "K-rap" "J-rap" "Nederhop (Dutch rap)" "Urban street" "Horrorcore"])) + +(def album-gen (gen/tuple artist-gen + (gen/vector album-name-gen + 2 5) + genre-gen + (gen/vector genre-gen + 2 5))) + + + +(defn get-albums-xs + [] + (map (fn [[artist album-xs genre sub-genre-xs] year id] + (cs/join \, + [id + (str year) + (cs/join \space + album-xs) + genre + (str "\"" + (cs/join \, + sub-genre-xs) + "\"")])) + (gen/sample-seq album-gen) + (mapcat (fn [year] + (repeat (rand-nth (range 10 15)) + year)) + (drop 2040 (range))) + (drop 501 (range)))) diff --git a/src/icw/data/process.clj b/src/icw/data/process.clj new file mode 100644 index 0000000..db9c5a3 --- /dev/null +++ b/src/icw/data/process.clj @@ -0,0 +1,241 @@ +(ns icw.data.process + (:require [clojure.string :as cs] + [icw.java-interop.jdbc :as jdbc] + [clojure.data.csv :as csv] + [clojure.string :as cs] + [icw.data.gen :as data-gen])) + + +;; Reading and processing data from resources/data/albumlist.csv + +(defonce album-lines (drop 1 + (line-seq (clojure.java.io/reader + "resources/data/albumlist.csv")))) + +(comment + (first album-lines)) + +;; Parsing + +(defn parse-line + "Input + line -> \"1,1967,Sgt. Pepper's Lonely Hearts Club Band,The Beatles,Rock,\"Rock & Roll, Psychedelic Rock\" + + Output + [\"1\" + \"1967\" + \"Sgt. Pepper's Lonely Hearts Club BandThe Beatles\" + \"The Beatles\" + \"Rock\" + [\"Rock & Roll\" \"Psychedelic Rock\"]" + [line] + ) + + +(comment + (= (parse-line "1,1967,Sgt. Pepper's Lonely Hearts Club Band,The Beatles,Rock,\"Rock & Roll, Psychedelic Rock\"") + ["1" + "1967" + "Sgt. Pepper's Lonely Hearts Club BandThe Beatles" + "The Beatles" + "Rock" + ["Rock & Roll" + "Psychedelic Rock"]])) + +(comment + (take 2 (map parse-line album-lines))) + + +(defn line-vec->line-map + "xs -> [\"1\" + \"1967\" + \"Sgt. Pepper's Lonely Hearts Club BandThe Beatles\" + \"The beatles\" + \"Rock\" + [\"Rock & Roll\" + \"Psychedelic Rock\"]] + Output + {:number \"1\" + :year \"1967\" + :artist \"The beatles\"p + :album \"Sgt. Pepper's Lonely Hearts Club BandThe Beatles\" + :genre \"Rock\" + :subgenre-xs [\"Rock & Roll\" + \"Psychedelic Rock\"]}" + [xs] + ) + +(comment + (= (line-vec->line-map ["1" + "1967" + "Sgt. Pepper's Lonely Hearts Club BandThe Beatles" + "The Beatles" + "Rock" + ["Rock & Roll" + "Psychedelic Rock"]]) + {:number "1" + :year "1967" + :artist "The Beatles" + :album "Sgt. Pepper's Lonely Hearts Club BandThe Beatles" + :genre "Rock" + :subgenre ["Rock & Roll" + "Psychedelic Rock"]})) + +(comment + (take 10 + (map #_FIXME + (map #_FIXME + album-lines)))) + + +(defn line-xs->album-xs + [line-xs] + ;; Use parse-line to convert list of strings to list of vectors + ;; Use line-vec->line-map to convert list of vectors to list of map + ) + +(defn populate-db + [] + (jdbc/init-db) + (let [albums (line-xs->album-xs album-lines)] + (doseq [album albums] + (jdbc/insert! (update-in album + [:subgenre] + #(cs/join "," %)))))) + +;; Check http://localhost:6789/albums + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Some more exploration with sequences +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn line-xs->rock-albums-xs + [line-xs] + (comment (map #_FIXME + (filter #_FIXME + #_FIXME)))) + +(comment (= (take 5 (line-xs->rock-albums-xs album-lines)) + '("Sgt. Pepper's Lonely Hearts Club Band" + "Pet Sounds" + "Revolver" + "Highway 61 Revisited" + "Exile on Main St."))) + + +(defn line-xs->albums-xs-before + "Lists all albums before 'year'" + [line-xs year] + (comment (filter #_FIXME + (map #_FIXME + #_FIXME)))) + +(comment (= (take 5 (map (juxt :year :album) + (line-xs->albums-xs-before 1987 + album-lines))) + '([1967 "Sgt. Pepper's Lonely Hearts Club Band"] + [1966 "Pet Sounds"] + [1966 "Revolver"] + [1965 "Highway 61 Revisited"] + [1965 "Rubber Soul"]))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Some more exploration with reduce +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn artists-with-range + "Artists who have most genres" + [line-xs] + (map #_FIXME + (sort-by #_FIXME + (reduce #_FIXME + {} + (map #_FIXME + line-xs))))) + +(comment (= (take 5 (artists-with-range album-lines)) + '("Various Artists" + "Bob Dylan" + "Ray Charles" + "Muddy Waters" + "Talking Heads"))) + + + +(defn find-popular-year + ;; Find top years for album releases + [line-xs] + (sort-by #_FIXME + (reduce #_FIXME + {} + (map #_ME + line-xs)))) + +(comment (= (take 5 (find-popular-year album-lines)) + '(["1970" 26] + ["1972" 24] + ["1973" 23] + ["1969" 22] + ["1971" 21]))) + +(defn find-popular-artists + [line-xs] + ) + + +(comment (= (find-popular-artists album-lines) + '(["The Beatles" 10] + ["Bob Dylan" 10] + ["The Rolling Stones" 9] + ["Bruce Springsteen" 7] + ["The Who" 7]))) + + +;; We can transform data a great deal with just map, reduce and filter + +;; Generally there are three patterns +;; Seq of N -> Seq of N (map) +;; Seq of N -> Seq of M (N > M) (filter) +;; Seq of N -> Any data structure (reduce) + +;; This is great but what about lazy sequences all we processed till now was in-memory data + + +;; A stream from future timeline from 2040 +data-gen/get-albums-xs + +;; DONT evaluate the function in REPL it's a lazy sequence. +;; It's a list of albums generated from 2040 till infinity + +(take 10 (data-gen/get-albums-xs)) + + +;; Let's some of the functions we created till now +(take 10 (line-xs->rock-albums-xs (data-gen/get-albums-xs))) + +;; We can use line-xs->albums-xs-before to just get limited set + +;; Right? + +(comment + ;; This will evaluate for infinity since filter does not stop evaluation + ;; We will just get infinite list of nils after year 2045 + (line-xs->albums-xs-before (data-gen/get-albums-xs) + 2045)) + + +;; https://clojure.org/api/cheatsheet +;; Espeically look for seq in and seq out + +(#_FIXME identity + (line-xs->albums-xs-before (data-gen/get-albums-xs) + 2045)) + +;; Try applying functions we have created till now + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; END of chapter 1 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Jump back to icw.core diff --git a/src/icw/data/process_faster.clj b/src/icw/data/process_faster.clj new file mode 100644 index 0000000..53ccdfe --- /dev/null +++ b/src/icw/data/process_faster.clj @@ -0,0 +1,382 @@ +(ns icw.data.process-faster + (:require [icw.java-interop.jdbc :as jdbc] + [clojure.string :as cs] + [icw.data.process :as idp])) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Concurrency +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; A way to get safe access to limited resources by multiple +;; actors/threads + +;; There are multiple paradigms models to do concurrency + +;; Locks are one way of doing it + +;; Most accurate Visualization of using locks +;; https://twitter.com/valarauca1/status/921542529962557440 + + +;; Clojure does an amazing job where there is immutable data by means of +;; persistant data structures provided by Clojure core + +;; But what about mutating things? + +;; There are four ways, + +;; vars +;; refs +;; agents +;; atoms + + +;; But first a brief introduction to running things in different threads + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Future +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +;; This will run on a different thread +(future 1) + +;; This will block and wait till it's complete +(deref (future 1)) + +;; A short hand for deref +@(future 1) + +(def f (future (println "hello") + 1)) + +;; Guess the output of second deref +@f +@f + +;; What's the point of deref then? + +;; Deref tries to get current value it +;; works on multiple things alongwith future + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; vars +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def x) + +(def x 1) + +;; Usually vars are static + +;; But they can be dynamic and have different value per thread + +(def ^:dynamic x 1) + +x + +(comment (binding [x 10] + (println x) + (future (println x)))) + +;; There are several reasons why dynamic variables are a bad idea +;; More on that later + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; refs +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Refs ensure safe mutation of shared state using STM + +;; STM to me looks something like this + +;; https://vimeo.com/106226560#t=10s + +;; dosync is macro to start transaction + +;; ref-set sets it to a value + +;; alter runs a function on the value + +;; Values held by refs must be immutable preferably clojure persistent +;; structures + +(def a-set (ref [])) +(def b-set (ref [])) + +(comment + (doseq [n (range 10)] + (future (dosync + (println "Transaction - " n) + (ref-set a-set n) + (Thread/sleep (rand-int 20)) + (ref-set b-set n))))) + +;; Why are there so many prints for just one run of 10 threads? +[@a-set @b-set] + + +(def a-alter (ref [])) +(def b-alter (ref [])) + +(comment + (doseq [n (range 10)] + (future + (dosync + (println "Transaction - " n) + (alter a-alter conj n) + (Thread/sleep (rand-int 20)) + (alter b-alter conj n))))) + +[@a-alter @b-alter] + +;; There is commute as well but we will not into details + +;; Exercise + +;; We want to keep record of last five database insertions that failed + +(comment + (let [failures-ref (ref []) + failure-num (ref 0)] + (doseq [n (range 10)] + (future (let [failure? (> (rand-int 100) + 30)] + (when failure? + (dosync + ;; Just to add randomness + (Thread/sleep (rand-int 20)) + + (#_FIME failures-ref + #_FIXME + {:thread-id (str "Thread-" n) + :failure-num @failure-num}) + + (#_FIME failure-num #_FIXME) + (when (> (count @failures-ref) 5) + (#_FIXME failures-ref + (into [] + (drop (- (count @failures-ref) + 5) + @failures-ref))))))))) + (Thread/sleep 1000) + (println @failures-ref) + (not= @failures-ref []))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; agent +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def agent-a (agent 1)) + +(send agent-a inc) +@agent-a + +(comment + (send agent-a (fn [n] + (/ n 0))) + + (agent-error agent-a) + (restart-agent agent-a 1) + + @agent-a + (send agent-a inc)) + +;; Agents are part of STM and send is parked work unless transaction is +;; complete + +(do (future (dosync (send agent-a inc) + (Thread/sleep 400))) + (println "1. Printing value of agent - " @agent-a) + (Thread/sleep 400) + (println "2. Printing value of agent - " @agent-a)) + +;; Agents are a great way to do async work on a value +;; Another good use case is to convert an thread unsafe API into a +;; thread safe one + +(defn thread-unsafe-api + [a x] + (println "Thread unsafe API - " x) + x) + +(def api-agent (agent "Message")) + +(send api-agent thread-unsafe-api "New message") + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Atoms +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Atoms are also meant to hold immutable values. Preferably Clojure's +;; persistent data structures + +(def state (atom {})) + +;; You can change the information of an atom using swap! or reset! + +(swap! state assoc :a 1) + +;; You can set a value using reset! + +(reset! state {:a 2}) + +;; Derefing the state will give you the current value +@state + +;; Values changed in swap! are atomic if there is a conflict the swap! +;; is retried + +;; Atoms are great way of doing sychrnous changes to shared state + +;; Atoms are used as a cache as well sometimes but be aware! +;; Atoms do not provide a way to limit memory usage. It's up to the user + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Promise +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; There are some more constructs which help co-ordiate between threads + +;; Deliver works only once on a promise +;; Deref works on promise as well +(let [p1 (promise)] + (future (Thread/sleep (rand-int 100)) + (deliver p1 "Thread 1")) + + (future (Thread/sleep (rand-int 100)) + (deliver p1 "Thread 2")) + + @p1) + +;; Promises are good to collect the first result from multiple threads/sources + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Delay +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; A very subtle difference than future. The body runs only after it's +;; deref and it only runs ons + +;; How many times "hello" will be printed? +(let [a (delay (println "hello") + 1)] + @a + @a) + +;; Compared to delay check when 'hello' is printed +(let [a (future (println "hello") + 1)] + @a + @a) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Rock paper scissors +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(def BEATS {:rock :scissors, :paper :rock, :scissors :paper}) + +(defn judge + [[name-1 move-1] [name-2 move-2]] + (cond + (= move-1 move-2) ::no-one + (= move-2 (BEATS move-1)) name-1 + :else name-2)) + +(defn run-a-turn + [game-room] + (let [[player-1 player-2] (keys (:players game-room)) + winner (judge [player-1 (rand-nth (keys BEATS))] + [player-2 (rand-nth (keys BEATS))])] + (if-not (= ::no-one winner) + (update-in game-room [:players winner] inc) + game-room))) + + +;;; Using agent + +(defn create-a-room + [name-1 name-2 room-name] + (agent {:players {name-1 0 + name-2 0} + :name room-name})) + +(defn run-game + [room-a] + (future (loop [] + (Thread/sleep 10) + ;; room-a is an agent we want to run a turn after every 10 + ;; msecs + (#_FIXME room-a #_FIXME) + (recur)))) + + +(defn run-multiple-games + "Run multiple games of rock-paper-scissors + On halt stop running the games and declare winners" + [n] + (let [game-rooms (map #(create-a-room "player-1" + "player-2" + (str "room-" %)) + (range n)) + running-games (doall (map (fn [room] + (run-game room)) + game-rooms))] + + + (Thread/sleep 5000) + + (doseq [game running-games] + (future-cancel game)) + + (map (fn [a] + [(:name @a) + (-> (sort-by (comp - second) + (:players @a)) + first + first)]) + game-rooms))) + + +;; Take home task modify run-multiple-games to count number of turns +;; that happened in each game + + +;; Coming back to populate-db + + +(defn populate-db + [] + (jdbc/init-db) + + (with-open [rdr (clojure.java.io/reader "resources/data/albumlist.csv")] + (let [lines (line-seq rdr) + albums (idp/line-xs->album-xs idp/album-lines)] + (doseq [album albums] + ;; Running it in another thread will help + (jdbc/insert! (update-in album + [:subgenre] + #(cs/join "," %))))))) + + +(comment (time (populate-db))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Take home exercise +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Make sure all the `jdbc/insert!` succeed + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Caveats +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; Limited threadpool for agent and future + +;; shutdown-agents needs to be called while exiting a service diff --git a/src/icw/java_interop/intro.clj b/src/icw/java_interop/intro.clj new file mode 100644 index 0000000..c4175ce --- /dev/null +++ b/src/icw/java_interop/intro.clj @@ -0,0 +1,111 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Preamble][Preamble:1]] +(ns icw.java-interop.intro + (:require [clojure.datafy :refer [datafy]])) +;; Preamble:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Simple%20method%20calls][Simple method calls:1]] +(do + ; Clojure strings are naturally Java String instances + (def sample-string "Hello, IN/Clojure!") + + (.length sample-string) + ; OR + (. sample-string length) + + (.toUpperCase sample-string) + ; OR + (. sample-string toUpperCase) + + (ns-unmap *ns* 'sample-string) + ) +;; Simple method calls:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Constructing,%20and%20property%20access][Constructing, and property access:1]] +(do + + ;; Constructor - note the dot in Point. + (def sample-point (java.awt.Point. 10 20)) + ; OR + (def sample-point (new java.awt.Point 10 20)) + + ;; Member access + (.-x sample-point) + ; OR + (. sample-point -x) + + (.-y sample-point) + ; OR + (. sample-point -y) + + ; Assignment + (set! (. sample-point y) 50) + + ;; Multiple, probably side-effecting, serial actions + (doto sample-point + (-> .-x println) + (-> .-y println)) ; The doto expression evaluates to the + ; supplied object. It may have mutated along the way within the doto. + + ; Bean there, done that? + (:x (bean sample-point)) + + (ns-unmap *ns* 'sample-point) + ) +;; Constructing, and property access:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Static%20access][Static access:1]] +(do + + ;; Static methods + (System/getProperty "java.vm.version") + + ;; Static values + Math/PI + + ; Threading-equivalent + (.. System getProperties (get "os.name"))) +;; Static access:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Building%20fluidly][Building fluidly:1]] +(do + (doto (java.util.HashMap.) + (.put :a :A) + (.put :b :B)) + + (doto (java.util.HashSet.) + (.add :a) + (.add :b) + (.add :a)) + + (doto (java.util.ArrayList.) + (.add 1) + (.add 2))) +;; Building fluidly:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:1]] +(clojure.pprint/pprint (do (datafy "hello world"))) +;; Objects:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:2]] +(comment (clojure.pprint/pprint (do (datafy sample-point)))) +;; Objects:2 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:3]] +(comment (clojure.pprint/pprint (do (bean sample-point)))) +;; Objects:3 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Objects][Objects:4]] +(clojure.pprint/pprint (do (bean {:a :A :b :B}))) +;; Objects:4 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Class][Class:1]] +(clojure.pprint/pprint (do (datafy java.awt.Point))) +;; Class:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Class][Class:2]] +(clojure.pprint/pprint (do (datafy java.util.Collections))) +;; Class:2 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/intro.org::*Namespaces][Namespaces:1]] +(clojure.pprint/pprint (do (datafy *ns*))) +;; Namespaces:1 ends here diff --git a/src/icw/java_interop/jdbc.clj b/src/icw/java_interop/jdbc.clj new file mode 100644 index 0000000..24acd37 --- /dev/null +++ b/src/icw/java_interop/jdbc.clj @@ -0,0 +1,76 @@ +(ns icw.java-interop.jdbc + (:require [clojure.data.csv :as csv] + [clojure.java.io :as io] + [clojure.string :as string] + [clojure.java.jdbc :as jdbc]) + (:import [com.zaxxer.hikari HikariConfig HikariDataSource] + [com.zaxxer.hikari.pool HikariPool])) + +(def hsqldb {:dbtype "hsqldb" + :dbname "albums"}) +(def albums-drop-table-ddl + (jdbc/drop-table-ddl :albums {:conditional? true})) + +(def albums-table-create-ddl + (jdbc/create-table-ddl + :albums + [[:number :int "PRIMARY KEY"] + [:year :int] + [:album "varchar(128)"] + [:artist "varchar(128)"] + [:genre "varchar(128)"] + [:subgenre "varchar(128)"]] + {:conditional? true})) + +(defn db-drop-albums-table! [db-spec] + (jdbc/db-do-commands + db-spec + [albums-drop-table-ddl])) + +(defn db-create-albums-table! [db-spec] + (jdbc/db-do-commands + db-spec + [albums-table-create-ddl + "CREATE INDEX year_idx ON albums (year)"])) + +(defn db-load! [db-spec table-name data] + (jdbc/insert-multi! db-spec + table-name + data)) + +(defn init-db + [] + (db-drop-albums-table! hsqldb) + (db-create-albums-table! hsqldb)) + +(defn insert! + [row] + (Thread/sleep (rand-int 20)) + (jdbc/insert! hsqldb + :albums + row)) + +(defn list-albums + [] + (jdbc/query hsqldb + ["SELECT * from albums;"])) + + +(do (db-drop-albums-table! hsqldb) + (db-create-albums-table! hsqldb)) + +(comment + (jdbc/query hsqldb ["SELECT count(*) from albums limit 1"]) + + (defonce hc (HikariConfig.)) + (doto hc + (.setJdbcUrl "jdbc:hsqldb:albums")) + (defonce hds (HikariDataSource. hc)) + (let [conn (.getConnection hds) + stmt (.createStatement conn) + res (.executeQuery stmt "SELECT * from albums limit 5")] + (jdbc/result-set-seq res)) + + (let [conn (.getConnection hds) + prepped-stmt (jdbc/prepare-statement conn "SELECT count(*) from albums")] + (jdbc/query conn [prepped-stmt]))) diff --git a/src/icw/java_interop/types.clj b/src/icw/java_interop/types.clj new file mode 100644 index 0000000..a96c558 --- /dev/null +++ b/src/icw/java_interop/types.clj @@ -0,0 +1,108 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*The%20Preamble][The Preamble:1]] +(ns icw.java-interop.types + (:require [icw.common :refer :all] + [clojure.datafy :refer [datafy]]) + (:import [java.util ArrayList Collections] + [java.util.concurrent Callable ForkJoinPool])) + +(defn java-interfaces [class-list] + (->> class-list + (map #(.getName %)) + (filter #(re-find #"^java\." %)))) +;; The Preamble:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*A%20core%20function][A core function:1]] +(-> + class ancestors java-interfaces) +;; A core function:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Map][Map:1]] +(-> {:a :A :b :B} class ancestors) +;; Map:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Set][Set:1]] +(-> #{:a :A :b :B} class ancestors) +;; Set:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Vector][Vector:1]] +(-> [1 2 3 4 5] class ancestors) +;; Vector:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*List][List:1]] +(-> (list 1 2 3 4 5) class ancestors) +;; List:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Record][Record:1]] +(defrecord FooBarBaz [foo bar baz]) +(-> FooBarBaz ancestors) +;; Record:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Comparable][Comparable:1]] +(let [a (ArrayList. [2 1 2 3 4 10 11 15 9])] + (Collections/sort a <) + ;; Hello, mutable-land! + a) +;; Comparable:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Search][Search:1]] +; You can directly use Clojure vectors with java.util.Collections +(Collections/binarySearch [1 2 3 4 5] 3) +;; Search:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*General%20ops][General ops:1]] +; Collections works well with vectors and lists +(Collections/min [1 2 3 4 5]) +(Collections/max '(1 2 3 4 5)) +(Collections/max [1 2 3 4 5]) +(Collections/reverse [1 2 3 4 5]) +;; General ops:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*General%20ops][General ops:2]] +;; And, does it work with sets? +(Collections/min #{3 4 1 7 10 Integer/MIN_VALUE}) +;; General ops:2 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*General%20ops][General ops:3]] +;; What about maps? +(Collections/min {1 :one 2 :two 3 :three}) +;; General ops:3 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Threads][Threads:1]] +; "Functions are first-class" should mean something, no? +(let [f (fn [] (println "Hello, from a runnable function.")) + t (Thread. f)] + ; Yes, IFn is Runnable! + (.run t)) +;; Threads:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Executors][Executors:1]] +; Runnable is a sad little thing. We want something happy! +(let [f (fn [] :woo-hoo!) + p (ForkJoinPool/commonPool) + ; Yes, IFn is Callable! + res (.submit p ^Callable f)] + ; Which means, we should be "get"ting results of our execution. + (.get res)) + +;; And the result is a... happy one! :woo-hoo! +;; Executors:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Records%20and%20Interfaces][Records and Interfaces:1]] +(clojure.pprint/pprint (do (do + "Yes, in Java, *everything* is an Object. So are our records" + (defrecord FooRecord [foo bar] + ; Which means, we make our toString a pretty one. + Object + (toString [_] (str "I am a holder of foo and bar. They are: " foo " and " bar))) + + ; Who are FooRecord's ancestors? + (ancestors FooRecord) + + ; Let's invoke our over-ridden method above... + (str (->FooRecord "foo" "bar")) + ; OR + (.toString (->FooRecord "foo" "bar"))))) +;; Records and Interfaces:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/java-interop/types.org::*Records%20and%20Interfaces][Records and Interfaces:2]] +"I am a holder of foo and bar. They are: foo and bar" +;; Records and Interfaces:2 ends here diff --git a/src/icw/search/common.clj b/src/icw/search/common.clj new file mode 100644 index 0000000..7cced12 --- /dev/null +++ b/src/icw/search/common.clj @@ -0,0 +1,8 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/search/common.org::*Common%20Search%20Routines][Common Search Routines:1]] +(ns icw.search.common + (:import [org.apache.lucene.store Directory] + [org.apache.lucene.analysis.standard StandardAnalyzer])) + +(defn standard-analyzer [] + (StandardAnalyzer.)) +;; Common Search Routines:1 ends here diff --git a/src/icw/search/core.clj b/src/icw/search/core.clj new file mode 100644 index 0000000..e109daa --- /dev/null +++ b/src/icw/search/core.clj @@ -0,0 +1,34 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/search/core.org::*The%20Preamble][The Preamble:1]] +(ns icw.search.core + (:require [icw.search + [common :refer :all] + [writer :as w] + [reader :as r]])) + +(defonce index (atom nil)) + +(defn reset-index! [] + (when @index + (.close @index) + (reset! index nil))) + +(defn init! [docs tokenized-fields & [re-init?]] + + (when (or re-init? false) + (reset-index!)) + + (when (nil? @index) + (let [d (w/directory) + analyzer (standard-analyzer) + iwc (w/index-writer-config analyzer) + iw (w/index-writer d iwc)] + (w/index! iw docs {:tokenized-fields tokenized-fields}) + (.close iw) + (reset! index d)))) + +(defn search [field query-term] + (r/search @index field query-term (standard-analyzer))) + +(comment + (search :subgenre "classic rock")) +;; The Preamble:1 ends here diff --git a/src/icw/search/reader.clj b/src/icw/search/reader.clj new file mode 100644 index 0000000..bea5eba --- /dev/null +++ b/src/icw/search/reader.clj @@ -0,0 +1,41 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/search/reader.org::*Code%20Template][Code Template:1]] +(ns icw.search.reader + (:require [icw.search.common :refer :all]) + (:import [org.apache.lucene.store Directory] + [org.apache.lucene.analysis Analyzer] + [org.apache.lucene.index IndexReader DirectoryReader] + [org.apache.lucene.document Field Document] + [org.apache.lucene.search Query IndexSearcher ScoreDoc] + [org.apache.lucene.util QueryBuilder])) +;; Code Template:1 ends here + +;; [[file:~/github/intermediate-clojure-workshop/content/search/reader.org::*Outline][Outline:1]] +(defn ^IndexReader index-reader [^Directory directory]) + +(defn ^IndexSearcher searcher [^Directory directory]) + +(defn field->kv [^Field f]) + +(defn doc->map [^Document doc]) + +(defn ^Query query [^clojure.lang.Keyword field + ^String term + ^Analyzer analyzer]) + +(defn score-docs->ids [^"[Lorg.apache.lucene.search.ScoreDoc;" score-docs]) + +(defn doc-ids->docs [^IndexSearcher searcher doc-ids]) + +; Create IndexSearcher given the index (Directory instance) +; Create a Query object given the field, term and analyzer +; .search on the IndexSearcher the generated Query +; Get .scoreDocs from the response +; ScoreDoc instances give you document-ids as handles +; Using the document-ids, get the Document instances from the IndexSearcher +; Convert the collection to maps with doc->map +; Enjoy the convenience of Clojure's support for Iterable +(defn search [^Directory directory + ^clojure.lang.Keyword field + ^String search-term + ^Analyzer analyzer]) +;; Outline:1 ends here diff --git a/src/icw/search/writer.clj b/src/icw/search/writer.clj new file mode 100644 index 0000000..9ff0732 --- /dev/null +++ b/src/icw/search/writer.clj @@ -0,0 +1,54 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/search/writer.org::*The%20Preamble][The Preamble:1]] +(ns icw.search.writer + (:require [icw.search.common :refer :all]) + (:import [org.apache.lucene.store Directory ByteBuffersDirectory] + [org.apache.lucene.analysis.standard StandardAnalyzer] + [org.apache.lucene.index IndexWriter IndexWriterConfig IndexOptions] + [org.apache.lucene.document Field FieldType Document])) + +(defn index-writer-config [analyzer] + (IndexWriterConfig. analyzer)) + +(defn index-writer [directory index-writer-config] + (IndexWriter. directory index-writer-config)) + +(defn directory [] (ByteBuffersDirectory.)) + +(defn field-type [{:keys [tokenize?]}] + (doto (FieldType.) + (.setIndexOptions IndexOptions/DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS) + (.setStored true) + (.setTokenized tokenize?))) + +(defn field [k v opts] + (let [ft (field-type opts) + v (str v)] + (Field. (name k) v ft))) + +(defn map->doc [m {:keys [tokenized-fields]}] + (let [ks (keys m) + d (Document.)] + (doseq [k ks] + (.add d (field k + (get m k) + {:tokenize? (contains? tokenized-fields k)}))) + d)) + +(defn index! [index-writer doc-maps opts] + (println (str "Indexing " (count doc-maps) " documents.")) + (doseq [doc-map doc-maps] + (.addDocument index-writer (map->doc doc-map opts)))) + +(comment + (def albums (:data/db.albums @icw.system/system)) + (first albums) + (def d (directory)) + (def analyzer (standard-analyzer)) + (def iwc (index-writer-config analyzer)) + (def iw (index-writer d iwc)) + (def opts {:tokenized-fields #{:album :artist :genre :subgenre}}) + (index! iw albums opts) + (.close iw) + (map icw.search.reader/doc->map (icw.search.reader/search d :album "revolver" analyzer)) + ) +;; The Preamble:1 ends here diff --git a/src/icw/system.clj b/src/icw/system.clj new file mode 100644 index 0000000..09fd5f9 --- /dev/null +++ b/src/icw/system.clj @@ -0,0 +1,58 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/system.org::*System][System:1]] +(ns icw.system + (:require [integrant.core :as ig] + [icw.data :as data] + [icw.search.core :as search] + [icw.web.core :as web] + [aleph.http :as http] + [icw.data])) + +(def config + {:data/db.albums {} + :data/db {:album-data (ig/ref :data/db.albums)} + :data/lucene {:album-data (ig/ref :data/db.albums)} + :services/streams.twitter {} + :services/streams.facebook {} + :services/streams.linkedin {} + :http/server {:port 6789, :handler (ig/ref :http/app)} + :http/app {}}) + +(defmethod ig/init-key :data/db.albums [_ _] + (data/load-album-csv-file data/album-csv-file)) + +(defmethod ig/init-key :data/db [_ _]) + +(defmethod ig/init-key :data/lucene [_ {:keys [album-data]}] + (search/init! album-data #{:album :artist :genre :subgenre})) + +(defmethod ig/init-key :services/streams.twitter [_ _]) + +(defmethod ig/init-key :services/streams.facebook [_ _]) + +(defmethod ig/init-key :services/streams.linkedin [_ _]) + +(defmethod ig/init-key :http/server [_ {:keys [handler] :as opts}] + (http/start-server handler (dissoc opts :handler))) +(defmethod ig/halt-key! :http/server [_ server] + (.close server)) + +(defmethod ig/init-key :http/app [_ _] + #'web/app) + +(defonce system (atom nil)) + +(defn start! [] + (if (nil? @system) + (do + (reset! system (ig/init config)) + nil) + (println "System already booted."))) + +(defn stop! [] + (when (some? @system) + (ig/halt! @system) + (reset! system nil))) + +(comment + (start!)) +;; System:1 ends here diff --git a/src/icw/web/core.clj b/src/icw/web/core.clj new file mode 100644 index 0000000..30e8021 --- /dev/null +++ b/src/icw/web/core.clj @@ -0,0 +1,42 @@ +(ns icw.web.core + (:require [compojure.core :as c] + [aleph.http :as http] + [ring.middleware + [keyword-params :refer [wrap-keyword-params]] + [params :refer [wrap-params]] + [json :refer [wrap-json-response]]] + [icw.web.handlers.albums :as albums] + [icw.search.core :as search])) + +(defn wrap-body [response-body] + {:status 200 + :body response-body}) + +(def albums-context + (c/context "/albums" [:as request] + (c/GET "/" [] + (wrap-body + (albums/list-albums))) + (c/GET "/:id" [id] + (wrap-body {:message (str "Here be your JSON of an album with id " id)})))) + +(def search-context + (c/context "/search" [:as request] + (c/GET "/:term" [term] + (let [{:keys [field]} (:params request) + field (or field "album")] + (or + (search/search (keyword field) term) + (wrap-body {:message (str "Looking for " term " in field " field ", eh?")})))))) + +(c/defroutes app* + albums-context + search-context) + +(def app + (c/routes + (-> #'app* + wrap-json-response + wrap-keyword-params + wrap-params))) +;; Tying Together:1 ends here diff --git a/src/icw/web/handlers/albums.clj b/src/icw/web/handlers/albums.clj new file mode 100644 index 0000000..589cba6 --- /dev/null +++ b/src/icw/web/handlers/albums.clj @@ -0,0 +1,9 @@ +(ns icw.web.handlers.albums + (:require [icw.java-interop.jdbc :as jdbc])) + +(defn list-albums + [] + (let [d (jdbc/list-albums)] + (if (seq d) + {:albums (jdbc/list-albums)} + {:message "Data is missing. Please populate the database."}))) diff --git a/src/user.clj b/src/user.clj new file mode 100644 index 0000000..63d4d9d --- /dev/null +++ b/src/user.clj @@ -0,0 +1,11 @@ +;; [[file:~/github/intermediate-clojure-workshop/content/user.org::*User%20namespace%20-%20defaults][User namespace - defaults:1]] +(ns user + (:require [clojure.pprint :as pp] + [clojure.datafy :refer [datafy]] + [integrant.core :as ig] + [integrant.repl :refer [clear go halt prep init reset reset-all]] + [icw.system :refer [start! stop!]])) + +(comment + (start!)) +;; User namespace - defaults:1 ends here