When learning Clojure, I created a repository on My GitHub, This helped in understand the power of Clojure as I am new and inexperienced Clojure programmer. It was difficult for me to adopt best practices provided by Clojure.
Clojure is dialect of lisp programming language. It is homoiconic language meaning the abstract syntax tree is same as the code. You code is your data and the code is representation of data structure such as list and vector
(defn fact [n] (reduce *' (range 1 (inc n))) This is covers the 80% of the syntax for this programming language. Notice, that the brackets (, [ ] ) are the vectors and lists whereas defn, facts, n (inside vector ) reduce all are symbols (data)
Homoiconic language also makes meta programming much easier. In meta programming, the code is designed analyse and transform programs while running.
In Clojure, there are distinction between state and identity. For example a person Fred will be known as him, as a entity throughout time. An identity has a particular state meaning that Fred age can increase from 25 to 26 but his identity does not changes or if Fred changes his name to Charles but again his identity does not changes. Clojure’s references types (Var, Atom, Ref and Agent) essentially with defined concurrency and change semantics that can hold any value and a stable identity.
Concurrency in Clojure
Clojure being a practical language, it allows to change state however these change in state ensure that the state remains inconsistent throughout the program.
Clojure provides the STM (Software transaction memory) for concurrency programming through, (dosync, ref, ref-set and alter). Atom support change in state in synchronous and independent manner. Agent changes state in asynchronous and independent manner. Whereas, Var support global change in state but in isolation manner with in the state.
In STM, it is atomic that is all the memory is touched at once otherwise does not happens. It is isolated, in much the memory transaction is independent.
For example if we threads (futures) which is changing the values by increment and then gets reset every time by the thread.In this until it is read and write another thread comes and modify it. The problem will appear is that the reading and writing the value in same time which is difficult.
(def a (atom 0)) (let [fs (for [_ (range 100)] (future (dotimes [_ 1000] (reset! a (inc @a)))))] (doseq [f fs] @f)) (deref a) ;; answers 57342
To fix this state changing problem by another thread. Loop over, and set value if it founds the old value. This will not fall into deadlock or race condition because, one thread has to restart until other thread has made an update. There is a restart in the thread.
(def a (atom 0)) (let [fs (for [_ (range 100)] (future (dotimes [_ 1000] (loop  (let [v @a] (when-not (compare-and-set! a v (inc v)) (recur)))))))] (doseq [f fs] @f)) (deref a) ;; answers 100000
This pattern is very common that Clojure has wrap it into a package called swap! and the below will give the same result
(def a (atom 0)) (let [fs (for [_ (range 100)] (future (dotimes [_ 1000] (swap! a inc))))] (doseq [f fs] @f)) (deref a)
Agent are integrated with STM. Agent are reactive, non-autonomous. Agent provides shared access to mutable state. Agent support independent, asynchronous changes of individual location. When the state of an agent is change it is immediately available to read by any thread.
Shamelessly taking great example from lethain
(ns agents-queue) (def logger (agent (list))) (defn log [msg] (send logger #(cons %2 %1) msg)) (defn create-relay [n] (letfn [(next-agent [previous _] (agent previous))] (reduce next-agent nil (range 0 n)))) (defn relay [relay msg] (letfn [(relay-msg [next-actor hop msg] (cond (nil? next-actor) (log "finished relay") :else (do (log (list hop msg)) (send next-actor relay-msg (+ hop 1) msg))))] (send relay relay-msg 0 msg))) (relay (create-relay 10) "hello") (. java.lang.Thread sleep 5000) (prn @logger) ; output from running script is: ; ("finished relay" (8 "hello") (7 "hello") ; (6 "hello") (5 "hello") (4 "hello") ; (3 "hello") (2 "hello") (1 "hello") ; (0 "hello"))
In the above code, create-relay creates an nested agent which has nil state, which can be represented as (agent (agent (agent nil))). Inside relay function it is taking two parameter, on is the agents and second is the message to relay. Inside relay-msg function it recursion performed by the agents like link list, they are getting out from nesting until last is executed and nil is left.
Notice that, thread will sleep for 5 second because, the above operation is asynchronous, therefore @logger would be executed before the relaying operation could completed.
Clojure on Browsers: Reagent
Reagent I found this great project. It will give the power of Clojure which is using repl. The best thing about using the Clojure for single-page application is that it will provide the support for multi-threading in the application.
Concurrency in Clojure can be achieved by Channels, Promise and Future. However, this is not covered because the main focus was the use of Software Transaction Memory, state and identity which helps to implement concurrency easily.