Day 3 focused on Clojure's support for concurrency. A big part of Clojure's concurrency story is support for the concept of a software transactional memory (STM). Much like a database transaction, modifications to mutable state are made within a transaction that the STM guarantees will be atomic, consistent and isolated. On top of the STM, Clojure provides a number of constructs to deal with shared across threads of execution, in particular we are given: atoms, refs and agents. Definitions of the differences are linked to, but at a basic level, all of these essentially 'wrap' a piece of data and allow you to change it according to a given set of semantics. You can provide additional semantics by providing a validator function that checks new values generated for a given piece of wrapped data.
Clojure's support for concurrency goes hand in hand with its preference for pure functions, for example, within the STM if a two competing threads end up trying to mutate a piece of data at the same time, one of them may be retried (the STM does not use locks to prevent the two from running concurrently but uses something akin to MVCC). Thus if a function has side effects these may execute more than one time, so one should only use pure functions when modifying mutable state. On to the exercises.
In the snippet above, the bank is a 'ref' this means we can use transactions marked by dosync to modify account balances without worrying about race conditions or inconsistency (I could have used atoms as well), we use the functions that clojure provides to mutate data within a tractions such as alter and ref-set. Alter basically takes a ref and a function whose return value will be the new value stored in that ref.
The next exercise was to write a solution to the sleeping barber problem (I had also tackled this in Io)
I hope this solution is correct. One thing to notice is how short this is (the code above is heavily commented). Another thing here is that we modify multiple refs within a given transaction. That is in fact, as i understand it, the main difference between refs and atoms. Modifications to refs within a transaction are coordinated, at the end of the transaction the changes to all the refs within it have to succeed for the transaction as a whole to succeed. Changes to atoms are independent and do not need to be in a transaction.
Also shown here is the future macro (as well as the future-call function), which is basically a way of getting a function to run on another thread. You can then check on the status of the return val from the future or block until you get a value back. Here it is just used to send customers to the barber for 10 seconds.
One thing i couldn't get to work that I would have liked, was to get the output of printlns within the body of a future to show; whenever i put some printlns within a future (or even the transactions) I wouldn't get any console output. Not sure what I need to do to get that to work.
I would also like to try and do this again and use agents to model the solution, as i didn't use agents at all and don't really know much about them. In anycase it was a fun set of exercises and I feel I got a good intro to clojure's concurrency support though there is a lot more to be seen I think. Overall I enjoyed the Clojure chapter and do want to dig into the language a bit more.
Other than that I also picked up "Programming Clojure" by Stuart Halloway as it was on sale, and its really quite good and very readable.