Clojure Koans 16 Refs Notebook
Hi! In this notebook I will attempt to break down refs in Clojure into skills, questions, and, perhaps, more.
There are spoilers below for the Clojure Koans.
Original Clojure Koans repository: https://github.com/functional-koans/clojure-koans/
{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
;; complient is used for autocompletion
;; add your libs here (and restart the runtime to pick up changes)
compliment/compliment {:mvn/version "0.3.9"}}}
{:hello (clojure-version)}
Initial Thoughts
TBD...
(def the-world (ref "hello"))
(def bizarro-world (ref {}))
;; skill 16001: Define the binding of a reference to a symbol (?)
;; "In the beginning, there was a word"
(= "hello" (deref the-world))
;; question 16001: What does `ref` stand for in Clojure?
;; answer 16001a: "Refs" (short for "references") are Clojure's way of dealing with immutability.
;; question 16002: What is a reference in Clojure and what does it do specifically?
;; question 16003: How does the `ref` function/macro work in Clojure?
;; question 16004: Is there any differences between a reference binding and a non-reference binding, and if so, what are they?
;; question 16005: What is the word "word" refering to?
;; question 16006: What is dereferencing explained in plain words?
;; skill 16002: Dereference a binding/reference (variable?) by using the `deref` function/macro (?)
;; "You can get the word more succinctly, but it's the same"
(= "hello" the-world)
;; skill 16003: Dereference a binding/reference (variable?) by using the `@` prefix (macro?)
;; "You can be the change you wish to see in the world."
(= "better" (do
(dosync (ref-set the-world "better"))
the-world))
;; skill 16004: Update/mutate/replace (?) a reference (?)
;; question 16007: Why is this wrapped in a `dosync` and `do` combo?
;; skill 16005: Use the `do` macro/function to exercute multiple forms/statements (?) and return the result of the final form/statement (?)
;; "Alter where you need not replace"
(= "better!!!" (let [exclamator (fn [x] (str x "!"))]
(dosync
(alter the-world exclamator)
(alter the-world exclamator)
(alter the-world exclamator))
the-world))
;; question 16008: What is the difference between `ref-set` and `alter`?
;; question 16009: What is the difference between replacing and altering?
;; skill 16006: Change the value bound to / held by (?) a ref (?) by using `alter`
;; knowledge: Asychronous work such as `ref-set`, `ref`, and `deref` needs to happen within a `dosync` to keep the work within a single "transaction" (?) becuase ... (?)
;; "Don't forget to do your work in a transaction!"
(= 0 (do (dosync (ref-set the-world 0))
the-world))
;; question 16010: What is a "transaction" in Clojure?
;; answer 16010a: ~~Anytime the value of a reference or object is changed.~~ (?)
;; Interactions with mutable containers are considered "transactions".
;; Transactions are `do` blocks where references to mutable state are contained within.
;; question 16011: What is the role of reification in transactions?
;; question 16012: Why are transactions (dosync) necessary/helpful?
;; answer 16012a: Transactions enable/allow "roll-back"
;;
;; skill 16007: Create a transaction by using the `dosync` macro/function (?)
;; question 16013: What does `do` do?
;; answer 16013a: The `do` makes the evaluation of conatined `do-sync`s eager.
;; question 16014: What does "eager" mean in the context of transations?
;; question 16015: What is the difference between "eager" and "lazy"?
;; answer 16015a: Conceptually speaking, lazy means to evaluate/execute only as needed, versus eager which means to evaluate/execute things as soon as they are encountered.
;; question 16016: What are concrete, concise examples of "eager" vs "lazy"
;; question 16017: What is the relevant context of "eager" and "lazy"?
;; answer 16017a: Data structures can be implemented themselves (intrinsic to the data structure) in an eager or lazy way.
;; "Functions passed to alter may depend on the data in the ref"
(= 20 (do
(dosync (alter the-world (+ % 20)))))
;; question 16018: My clj-kondo linter tells me that the `do` is redundant. Is this indeed true and if yes why?
;; question 16019: Why might a Clojure programmer prefer `partial + 20` rather than `#(+ % 20)` for the above solution?
;; question 16020: What are the differences between the current solution and this `alter the-world + 20` ?
;; "Two worlds are better than one"
(= ["Real Jerry" "Bizarro Jerry"]
(do
(dosync
(ref-set the-world {})
(alter the-world assoc :jerry "Real Jerry")
(alter bizarro-world assoc :jerry "Bizarro Jerry")
[(the-world :jerry) (bizarro-world :jerry)])))
;; question 16021: Why are `[(@the-world :jerry) (@bizarro-world :jerry)]` and the same minus the `@` deref prefixes functionally equivalent here? Or are they not but just appear to be?
;; question 16022: Where and when is it critical to deref?
;; question 16023: What are typical/common gotcha's in Clojure referencing/dereferencing?
;; question 16024: What are some more useful/helpful/clear/concrete examples of `do` and `dosync` used in context?
Closing Thoughts
TBD...
2021_05_3 Update:
On the theme of doing better, I'd like to continue to update these notebooks. A list of ideas and to-do's:
[ ] Share this and all notebooks in Clojureverse, to facilitate effective collaboration
[ ] Review the clojure.org reference on refs (pun not intended)
[ ] Update the content in this notebook by answering questions & clarifying skills
[ ] Demo some practical use-cases for refs
[ ] Get mentor/export level feedback on what are hard requirements vs "sensible" pre-requisites for learning and using refs in Clojure code
;; alternate solution provided by my friend Naxels
(= ["Real Jerry" "Bizarro Jerry"]
(do
(dosync
(ref-set the-world {})
(alter the-world assoc :jerry "Real Jerry")
(alter bizarro-world assoc :jerry "Bizarro Jerry")
(map :jerry [the-world bizarro-world]))))