FP Abstractions + PR Merged

Jul 8, 2020


What I learned Today

Chapter 5 of Brave Clojure is all about Pure Functions and Immutable Data Structures.

Pure Functions -

They only rely on their own args and immutable values.

This gives us stability and consistency since we don’t have to worry how the functions are going to impact other parts of your system.

Examples -

(+ 1 2) ; => Arithmetic is always referentially transparent

(defn wisdom
  (str words ", Daniel-san"))

(wisdom "Never clip your toes on a Tueday")
;; => "Never clip your toes on a Tueday, Daniel-san"

This function is not referentially transparent since it relies on a random number

(defn year-end-evaluation
  (if (> (rand) 0.5)
    "You get a raise!"
    "Better luck next year :-("))

;; => "Better luck next year :-("

;; => "You get a raise!"

;; => "Better luck next year :-("

Evertime we call the function, we get a different answer.

Side fx

To perform a side effect is to change the association between a name and its value within a given scope.

Side effects allow you to interact with the real world. However, now you have to be careful about what the names in your code are referring to.

Immutable Data Structures

Functional Alternative to Mutation is Recursion

(defn sum
  ; -> arity overloading to provide a default val of 0
  ([vals] (sum vals 0))
  ([vals accum]
   (if (empty? vals) ; -> recursion base case
     (sum (rest vals) (+ (first vals) accum)))))

(sum [39 5 1])
;; => 45

Each recursive call to sum creates a new scope where vals and accum are bound to different values, without needing to alter the originally passed values

(defn sum-recur
(sum vals 0))
([vals accum]
(if (empty? vals)
(recur (rest vals) (+ (first vals) accum)))))

(sum-recur [45 54 123])
;; => 222

Function Composition instead of Attribute Mutation

(require '[clojure.string :as s])
(defn clean
  (s/replace (s/trim text) #"lol" "LOL"))

(clean "My boa constrictor is so sassy lol!       ")
;; => "My boa constrictor is so sassy LOL!"

;; => "My boa constrictor is so sassy LOL!"

Functional Programming encourages you to build more complex functions by combining simpler functions.

FP is -

OOP -> modify data by embodying it as an object. original data is lost FP -> Data is unchanging and you derive new data from existing data

comp allows you to compose pure functions

((comp inc *) 2 3)
;; => 7

;; (comp f1 f2 f3) -> creates an anonymous function that composes the results of the args passed to it
;; (f1 (f2 (f3 x1 x2 x3)))
;; The innermost function (the last arg to comp) can take any number of args, but each of the successive functions must be able to take only one arg
(def character
  {:name "Smooches McCutes"
   :attributes {:intelligence 10
                :strength 4
                :dexterity 5}})

(def c-int (comp :intelligence :attributes))
(def c-str (comp :strength :attributes))
(def c-dex (comp :dexterity :attributes))

(c-int character)
;; => 10

(c-str character)
;; => 4

(c-dex character)
;; => 5

Memoization lets you take advantage of referential transparency by storing the arguments passed to a function and the return value of the function.

(defn sleepy-identity
  "Returns the given value after 1 second"
  (Thread/sleep 1000)

(sleepy-identity "Hello")
;; => "Hello" took 1 second
;; subsequent calls will also return "hello" after sleeping for 1 second

(def memo-sleepy-identity (memoize sleepy-identity))

(memo-sleepy-identity "Henlo Frens")
;; => "Henlo Frens" took 1 second

(memo-sleepy-identity "Henlo Frens")
;; => "Henlo Frens" returned immediately

(memo-sleepy-identity "Different arg")
;; => "Different arg" took 1 second

Chapter 5 Exercises

  1. You used (comp :intelligence :attributes) to create a function that returns a character’s intelligence. Create a new function, attr, that you can call like (attr :intelligence) and that does the same thing.
(def attr #(get-in character [:attributes %]))
  1. Implement the comp function

comp function syntax -> (comp f g h …) comp takes a variable number of arguments (f (g (h …)))

(defn my-comp
  [& args]
  (fn [& more-args]
    (let [i (apply (last args) more-args)
          rest-args (reverse (drop-last 1 args))]
      (reduce (fn [acc v] (v acc)) i rest-args))))
  1. Implement the assoc-in function. Hint: use the assoc function and define its parameters as [m [k & ks] v]
(defn my-assoc-in
  [m [k & ks] v]
  (println m k ks v)
  (if (empty? ks)
    (assoc m k v)
    (assoc m k (my-assoc-in (get m k) ks v))))
  1. Look up and use the update-in function
(update-in {:hello {:si {:hi 4}}} [:hello :si :hi] + 20)
;; => {:hello {:si {:hi 24}}}
  1. Implement update-in
(defn my-update-in
  [m [k & ks] f & args]
  (println m k ks f args)
  (if (empty? ks)
    (assoc m k (apply f args))
    (assoc m k (my-update-in (get m k) ks f args))))

(update-in {:hello {:si {:hi 4}}} [:hello :si :hi] + 20 3333)
;; => {:hello {:si {:hi 3357}}}

Athens PR Merging

Today was also the day our collaborative PR got merge. But not before a little Code Review. The code review was pretty useful since it helped me understand best practises when it comes to the codebase. Resolving the issues raised in the code review was pretty easy and subsequently the PR was merged into the Athens codebase.


I also spent quite some time today trying to debug a really weird issue with the Daily Notes feature in Athens where creating the 9th daily note would corrupt the internal sort order for the Datoms for Athens.

I wasn’t able to find a solution to this issue, but it was still interesting to delve into the code to try and figure it out.

Other than that, I’m glad I was able to finish Ch5. Tomorrow I will aim for Chapter 6 along with some DataLog study.