Seven Languages in Seven Weeks Part 10: Clojure Day 3
by Justin Michalicek on May 29, 2011, 6:59 a.m. UTCI finally got through day 3 of Clojure. The last exercise was pretty rough and took several hours to get figured out. I also never posted the day 2 stuff, so I'll toss that in here. I was only able to do one day 2 exercise. The version of Clojure that comes with Debian 6 is only 1.1 and so does not support defprotocol and defrecord and I didn't feel like downloading and building a newer version since I'm not likely to use it again.
The day 2 exercise I completed was to implement an unless statement with an else condition using macros. Here it is.
user=> (defmacro unless [test body elsebody] (list 'if (list 'not test) body elsebody)) #'user/unless user=> (unless true (println "false") (println "true")) true nil user=> (unless false (println "false") (println "true")) false nil
The day 3 exercises were a good bit more difficult. The first one wasn't too bad. The exercise was to use refs to create a vector of accounts. Then create debit and credit functions to change the balance of the account. I suspect the book wanted me to use defprotocol and defrecord, but since I didn't have access to those I created a vector which just held the balances and edited those directly by passing the offset of the account I wanted to edit.
(defn create [] (ref (vector))) (defn debit [cache key value] (dosync (alter cache assoc key (- (nth @cache key) value)) ) ) (defn credit ([cache key value] (dosync (alter cache assoc key (+ (nth @cache key) value)) )) ([cache value] (dosync (alter cache conj value))) ) user=> (def accounts (create)) #'user/accounts user=> (credit accounts 15) [15] user=> @accounts [15] user=> (credit accounts 10) [15 10] user=> (credit accounts 0 35) [50 10] user=> (debit accounts 1 3) [50 7]
The second exercise was much more difficult, although it looks quite simple with the code laid out right there. It was to solve a problem called The Sleeping Barber. The basic problem goes like this. You've got a barber shop with 3 chairs and one barber. Every 10-30 milliseconds a customer comes in and sits in a chair if one is open, otherwise they leave. The barber will pull customers from a chair, leaving that chair open for another customer, and cut their hair which takes 20 milliseconds. Then at the end, tell how many customers the barber handled in 10 seconds.
This required multiple threads, which I handled with agents and recursive functions, which brought in customers, waited, and cut customers hair. My list of seats was an atom of a list.
(def customer_count (atom 0)) (def seat_list (atom ())) (defn addCustomer [seats] (comment (println "Adding customer")) (swap! seats conj 1) ) (defn customerLoop [seats] (loop [s seats] (Thread/sleep (+ 10 (rand-int 20))) (if (< (.size @s) 3) (addCustomer s) (println "Seats full") ) (recur s) ) ) (defn handleCustomer [seats count] (comment (println "Handling customer")) (swap! seats pop) (swap! count + 1) (Thread/sleep 20) ) (defn barberLoop [seats] (loop [s seats] (if (> (.size @s) 0) (handleCustomer s customer_count) (println "No customers") ) (recur s) ) ) (comment waits for 10 secs and then prints the tally of customers served) (defn tally [x] (Thread/sleep 10000) (println "Handled this many customers in 10 seconds") (println @x) (System/exit 0) ) (def send_customers (agent seat_list)) (def cut_hair (agent seat_list)) (send send_customers customerLoop) (send cut_hair barberLoop) (def final_count (agent customer_count)) (send final_count tally)
My final thoughts are that Clojure is interesting and made me think about things differently and has some great concepts for concurrency, but I'll likely never use it again. I'm too set in my OO ways and much prefer languages like Python, Ruby, and Scala which let me have an OO style architecture but use functional ideas where they make sense.