Overthunk

Core Clojure Functions and Athens DevCards

Jul 5, 2020


Contents

What I learned today

Chapter 4 of Brave Clojure

Clojure implements functions in terms of sequence abstractions. map, reduce take a seq. If the core sequence functions (first, rest and cons) work on a DS, it implements the sequence abstraction.

What is a Sequence? -

A sequence is a collection of elements organized in linear order vs an unordered collection or a graph without a before-after relationship between its nodes.

Whenever a Clojure function expects a seq, it uses the seq function on the data structure in order to obtain a data structure that allows for first, rest and cons.

seq always returns a value that looks like a list

(seq {:name "Billy" :age 43})
;; => ([:name "Billy"] [:age 43])
;; We get a list of two element vectors

A function like map, or reduce only cares that the data structure it receives can have sequence operations performed on them, not the implementation details.

(defn titleize
  [topic]
  (str topic " for the Brave and True"))

;; vectors
(map titleize ["Hamsters" "ragnarok"])
;; => ("Hamsters for the Brave and True" "ragnarok for the Brave and True")

;; list
(map titleize '("Table Tennis" "Empathy"))
;; => ("Table Tennis for the Brave and True" "Empathy for the Brave and True")

;; set
(map titleize #{"Hamsters" "ragnarok"})
;; => ("Hamsters for the Brave and True" "ragnarok for the Brave and True")

You can do interesting things with the map function like passing a collection of functions as an argument

(def sum #(reduce + %))
(def avg #(/ (sum %) (count %)))
(defn stats
  [numbers]
  (map #(% numbers) [sum count avg]))

(stats [1 2 3 4])
;; => (10 4 5/2)

(stats [80 1 44 13 6])
;; => (144 5 144/5)

or even use map to get the values associated with a keyword from a collection of maps. (since keywords can be used as functions)

(def identities
  [{:alias "Batman" :real "Bruce Wayne"}
   {:alias "Spiderman" :real "Peter Parker"}
   {:alias "Santa" :real "Your mom"}
   {:alias "Easter Bunny" :real "Your dad"}])

(map :real identities)
;; => ("Bruce Wayne" "Peter Parker" "Your mom" "Your dad")

These were two pretty interesting use-cases for map that I hadn’t learned about till now. Thanks Brave Clojure!

Like map, reduce is also a very flexible function that can be used for more things than commonly assumed.

We can use reduce to update a maps values -

(reduce (fn [new-map [key val]] (assoc new-map key (inc val))) {} {:max 30 :min 10})
;; => {:max 31, :min 11}

reduce can also be used to build a larger sequence from a smaller one. It can be used to filter values from a collection etc. reduce can basically be used to derive new values from a seq-able data structures.

Athens Code

I saw an issue about refactoring a small piece of Athens code. I figured that this would be a good way to understand part of the codebase by myself and potentially contribute.

The issue related to swapping out the create-element function in reagent with adapt-react-class.

The function reagent/adapt-react-class will turn a React Component into something that can be placed into the first position of a Hiccup form, as if it were a Reagent function

The code change itself is quite simple -

[:span {:style {:color (color :link-color)}} (r/create-element mui-icons/Face)]

;; becomes

[(r/adapt-react-class mui-icons/Face) {:style {:color (color :link-color)}}]

We can skip on using the span keyword and the new code makes more semantic sense since we’re rendering the mui-icons/Face component with {:style {:color (color :link-color)}} props.

I also got to test out building DevCards and deploying the commit with GitHub Actions!

Takeaways

Even the smallest issues can end up teaching you something! It was really fun to explore this little part of the Athens codebase and learn about reagent functions.

I think I achieved what I wanted to today and learned a bit more than I expected which is always quite pleasant. I hope this will give me confidence to dig into more issues and become a better contributor.