Clojure Training with Lambda Next · Day 2 - olange/learning-clojure GitHub Wiki

Day 1 / Day 2 / Day 3

Hands-on

## Reference types

The Uniform update model: (updating-fn main-value pure-fn)

Atoms

(def a (atom []))
(= (deref a) @a)
(defn sleepy-conj [v x t] (println "v x t:" v x t) (Thread/sleep t) (conj v x))
(future (swap! a sleepy-conj :sleepy 10000))
(swap! a conj :awake)
@a
(def a (atom []))
(def b [1])
(reset! a b) => [1]
(compare-and-set! a b [101]) => true
@a => [101]

Agents

Non-blocking updates.

(def a (agent []))
(send a conj 13)
@a

All the actions sent to an agent are guaranteed to be executed in the order they are sent.

(def a (agent []))
(send a sleepy-conj 13 2000)
(send a conj :awake)
@a

You spend your days in spend, unless the action is doing IO. When the action takes more time, use the send-off thread pool.

(def a (agent []))
(send a sleepy-conj :sleepy 2000)
(send a conj :awake)
(send-off a sleepy-conj :long-computation 20000)
@a
  • *agent* this is bound to «me» in an agent
  • await / await-for
  • shutdown-agents

Exception handling

(def a (agent []))
(send a inc)
@a => []
(send a conj :awake) => returns the exception of previous erroneous action
@a => []
(agent-errors a)
(restart-agent a [])
@a => []

There are options on agent to define its behavior in front of errors

  • (def a (agent [] :error-mode :continue)))
  • (def a (agent [] :error-handler #(println "EEP" %&))) :error-mode :continue is implicit in this case

### Refs

Come for the STM, stay for the rest. Coordination.

The first dosync block hereafter would retry the (transfer a1 a2 20) if the value of from-a or to-a would be changed by another thread between the two alter.

(def a1 (ref 100))
(def a2 (ref   0))
@a1
@a2

(defn transfer [from-a to-a x]
  (alter from-a - x)
  (alter to-a   + x))
  
(dosync
  (transfer a1 a2 20))

(future
  (dosync
    (println @a1) / (println (ensure a1))
    (Thread/sleep 10000)
    (println @a2) / (println (ensure a2))))

(dosync
  [@a1 @a2])

Above we have a consistent view at the end of the transaction (dosync blocks). To get a consistent view at the beginning of the transaction, define ref-history-count.

(ref-history-count a1)
(dosync
  [@a1 @a2])
  • (dosync (ref-set a1 120)) forces a new value in a1 (acts like reset! from atoms)
  • (dosync (commute a1 inc)) where dosync blocks would restart all computations if they contain alter functions, they would only restart the commute function calls if the state of one ref was changed during the execution in the dosync block

### Validators

(defn ensure-pos [x] (>= x 0))
(def r1 (ref 100 :validator ensure-pos))
(def t1 (atom 100 :validator ensure-pos))
(def a1 (agent 100 :validator ensure-pos))

### Validators

Might come to help for quick debugging; Christophe mentioned he never encountered a use case for them.

(def a (atom []))
(defn spit [k r o n] (println k r o n))
(add-watch a :spit-watch spit)
(swap! a conj :something)
(reset! a [])

### About exclamation marks

Exclamation marks on function names signal that the function should not be used from inside a transaction.

  • swap!
  • reset!
  • alter / send / send-off / commute no exclamation mark, because these functions are STM-aware and will only be executed after transaction committed
  • (io! ...) functions like println should actually have an exclamation mark; io! can be used to guard against I/O or side-effects within transactions -- which shouldn't happen, as transactions might be restarted.

### Future, delay, promise

(def f (future (println "") :hello-from-future)
@f
(deref f 1000 [])
(def d (delay (println "I'm running now") :hello-from-delay))
@d
(def p (promise))
(future (println (deref p 5000 :timeout)))
(deliver p "Finally !!")

### Delay and atom, and memoize

(defn mymemoize [f]
  (let [cache (atom {})]  ;; the cache will be part of lexical scope of the anonymous function hereafter
  	(prn cache)
  	(fn [& xs]
  	  (if-let [[k v] (find @cache xs)] ;; we use find rather than get because the value in the cache might be nil or false, whereas find would return a map entry ([<key> nil])
  	  	v
  	  	(let [new-val (apply f xs)]
  	  	  (swap! cache assoc xs new-val)
  	  	  new-val)))))

But it has the problem that ...

(def mf1
  (mymemoize
    (fn ... (mf1)
    ()))

(didn't get it, couldn't follow what was happening in the REPL; Edmund is moving around at blazing speed! I'm amazed)

(defn mymemoize [f]
  (let [cache (atom {})]  ;; the cache will be part of lexical scope of the anonymous function hereafter
  	(prone cache)
  	(fn [& xs]
  	  (if-let [[k v] (find @cache xs)] ;; we use find rather than get because the value in the cache might be nil or false, whereas find would return a map entry ([<key> nil])
  	  	v
  	  	@(get (swap! cache assoc xs (delay (apply f xs))) xs))))

## Polymorphism

### Multimethods


(def set-val nil) ;; a trick to allow redefining the multi-method dispatch function

(defn html-dispatch
  [{ tag :tag {type :type :as attrs} :attrs :as element}
   value-inside]
  (cond
  	(and (= tag :input)
  		 (= type :text))
  	  :text-element

  	(and (= tag :input)
  		 (= type :radio))
  	  (if-not (string? value-inside)
  	  	:boolean-radio
  	  	:string-radio)

    :else :default))

(defmulti set-val
  "Will change the representation of an HTML element"
  #'html-dispatch)   ;; another trick to allow changing the dispatching function

;; An element is a map { :tag ..., :attrs ..., :content [...] }
(defmethod set-val
  :default
  [element value-inside]
  (assoc element :content [value-inside]))

(defmethod set-val
  :text-element
  [element value-inside]
  (assoc-in element [:attrs :val] value-inside))

(defmethod set-val
  :boolean-radio
  [element value-inside]
  (if value-inside
	(assoc-in  element [:attrs :checked] "checked")
	(update-in element [:attrs] dissoc :checked)))

(defmethod set-val
  :string-radio
  [element value-inside]
  (if (= value-inside (get-in element [:attrs :name]))
	(update-in element [:attrs] dissoc :checked)
	(assoc-in  element [:attrs :name] value-inside)))

(set-val {
	:tag     :p
	:attrs   {}
	:content [] }
	"Something")

(set-val {
	:tag     :input
	:attrs   { :type :text }
	:content [] }
	"Nothing")

(set-val {
	:tag     :input
	:attrs   { :type :radio }
	:content [] }
	true)

(set-val {
	:tag     :input
	:attrs   { :type :radio :checked "checked"}
	:content [] }
	false)

(set-val {
	:tag     :input
	:attrs    { :type :radio :name "Christophe" }
	:content [] }
	"Wizard")

One of the problems of multimethods is that although new methods can be added, the dispatching function can't be adjusted without access to the source code.

Predicate dispatch

Wishful thinking; predicate dispatch is a tempting feature, that no one has done right for now; its a research problem. Would solve a lot of the expressiveness problems of the multimethods.

(defmulti name)

(defmethod name pred?
  [...]
  body)

(defmethod set-val [{:tag :input :attrs {:type "radio"}} Boolean]
  ...
  )
⚠️ **GitHub.com Fallback** ⚠️