Skip to content

Latest commit

 

History

History
180 lines (136 loc) · 6.48 KB

2020-07-13.md

File metadata and controls

180 lines (136 loc) · 6.48 KB

Learning Clojure in Public - Week 4 Day 1 (22/35)

Expectations

This is the start of week 4 and oh my, has the time gone quickly. I feel like there is so much more to learn... Hopefully I will be done with Brave Clojure Soon, will have reached 100 problems on 4clojure and then will have time to focus on actual Athens issues... Maybe read the reframe documentation.

Today I hoped to advance as much as I can in Brave Clojure's Chapter 10, and hopefully do some 4clojure problems as well.

What I learned

Functional Programming

Contrary to OOP, in FP you don't modify an entity, you create a new one derived from the original one. The value doesn't change, you apply a process to a value (or not) to get a new value.

In this paradigm, state means the value of an identity at a point in time.

Rich Hickey has used the analogy of phone numbers to explain state. Alan’s phone number has changed 10 times, but we will always call these numbers by the same name, Alan’s phone number. Alan’s phone number five years ago is a different value than Alan’s phone number today, and both are two states of Alan’s phone number identity.

Atoms

                 :percent-deteriorated 0}))

This creates a new atom and binds it to the name fred. This atom refers to the value {:cuddle-hunger-level 0 :percent-deteriorated 0}, and you would say that that’s its current state. To get an atom’s current state, you dereference it. Here’s Fred’s current state:

=> {:cuddle-hunger-level 0, :percent-deteriorated 0}

In this case, dereferencing an atom will never block. We use swap! to update an atom:

(swap! fred
       (fn [current-state]
         (merge-with + current-state {:cuddle-hunger-level 1
                                      :percent-deteriorated 1})))
; => {:cuddle-hunger-level 2, :percent-deteriorated 1}

Or another way is to use swap! with the result of a function (and taking a state):

(swap! fred increase-cuddle-hunger-level 10)
; => {:cuddle-hunger-level 12, :percent-deteriorated 1}

@fred
; => {:cuddle-hunger-level 12, :percent-deteriorated 1}

A new function (to me) also helps with updating state, update-in:

(update-in {:a {:b 3}} [:a :b] inc)
; => {:a {:b 4}}

(update-in {:a {:b 3}} [:a :b] + 10)
; => {:a {:b 13}}

It can be used like this:

(swap! fred update-in [:cuddle-hunger-level] + 10)
; => {:cuddle-hunger-level 22, :percent-deteriorated 1}

Another great things about atoms is that you can still access previous versions of the state, like so:

(let [num (atom 1)
      s1 @num]
  (swap! num inc)
  (println "State 1:" s1)
  (println "Current state:" @num))
; => State 1: 1
; => Current state: 2

swap! implements compare-and-set semantics, meaning it does the following internally:

  • It reads the current state of the atom.
  • It then applies the update function to that state.
  • Next, it checks whether the value it read in step 1 is identical to the atom’s current value.
  • If it is, then swap! updates the atom to refer to the result of step 2.
  • If it isn’t, then swap! retries, going through the process again with step 1.

swap! updates do happen synchronously.

To update a atom without reading its value we can use the reset! function, (reset! fred {:new 0}).

Watches

A watch takes 4 arguments: a key, the reference being watched, its previous state, and its new state. It allows to check on a reference type's every move.

Let's say you have a shuffle-speed function:

(defn shuffle-speed
  [zombie]
  (* (:cuddle-hunger-level zombie)
     (- 100 (:percent-deteriorated zombie))))

And you want to be alerted when the shuffle speed is above a certain level. You can do the following. add-watch attaches the function to fred

(defn shuffle-alert
  [key watched old-state new-state]
  (let [sph (shuffle-speed new-state)]
    (if (> sph 5000)
      (do
        (println "Run, you fool!")
        (println "The zombie's SPH is now " sph)
        (println "This message brought to your courtesy of " key))
      (do
        (println "All's well with " key)
        (println "Cuddle hunger: " (:cuddle-hunger-level new-state))
        (println "Percent deteriorated: " (:percent-deteriorated new-state))
        (println "SPH: " sph)))))

(reset! fred {:cuddle-hunger-level 22
              :percent-deteriorated 2})
(add-watch fred :fred-shuffle-alert shuffle-alert)
(swap! fred update-in [:percent-deteriorated] + 1)
; => All's well with  :fred-shuffle-alert
; => Cuddle hunger:  22
; => Percent deteriorated:  3
; => SPH:  2134

(swap! fred update-in [:cuddle-hunger-level] + 30)
; => Run, you fool!
; => The zombie's SPH is now 5044
; => This message brought to your courtesy of :fred-shuffle-alert```
### Validators
__Validators__ let you specify what states are allowable for a reference. For example, here’s a validator that you could use to ensure that a zombie’s `:percent-deteriorated` is between 0 and 100:
```(defn percent-deteriorated-validator
  [{:keys [percent-deteriorated]}]
  (and (>= percent-deteriorated 0)
       (<= percent-deteriorated 100)))

And this is how you attach a validator:

(def bobby
  (atom
   {:cuddle-hunger-level 0 :percent-deteriorated 0}
    :validator percent-deteriorated-validator))
(swap! bobby update-in [:percent-deteriorated] + 200)
; This throws "Invalid reference state"

It's even possible to throw an exception to get a more descriptive message:

(defn percent-deteriorated-validator
  [{:keys [percent-deteriorated]}]
  (or (and (>= percent-deteriorated 0)
           (<= percent-deteriorated 100))
      (throw (IllegalStateException. "That's not mathy!"))))
(def bobby
  (atom
   {:cuddle-hunger-level 0 :percent-deteriorated 0}
    :validator percent-deteriorated-validator))
(swap! bobby update-in [:percent-deteriorated] + 200)
; This throws "IllegalStateException: That's not mathy!"

Takeaways

I went a step further on atoms, which is used to manage state. To manage concurrent state updates I will (re)learn refs, which is kind of exciting given the new information I gathered about atoms today.

I learned about validating a reference, or getting notified on changes in a reference. I also learned that atoms can retain their past states (which is pretty exciting if you ask me!). Let's end the day with a tally of what I completed

  • First half of Chapter 10 of Clojure for the Brave and True
  • 2 4clojure problems