Clojure Training with Lambda Next · Day 2 - olange/learning-clojure GitHub Wiki
- Tron Bots Contest continued; the longest path technique won most of the times
## Reference types
The Uniform update model: (updating-fn main-value pure-fn)
(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]
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 ina1(acts likereset!from atoms) -
(dosync (commute a1 inc))wheredosyncblocks would restart all computations if they containalterfunctions, they would only restart thecommutefunction calls if the state of one ref was changed during the execution in thedosyncblock
### 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/commuteno exclamation mark, because these functions are STM-aware and will only be executed after transaction committed -
(io! ...)functions likeprintlnshould 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.
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]
...
)