Overthunk

Ch 4 of Brave Clojure + 2nd Athens PR

Jul 7, 2020


Contents

Ch4 of Brave Clojure

Lazy Seq - A recipe for how to realize the elements of a sequence and the elements that have been realized so far. Everytime you try to access an unrealized element, the lazy seq will use its recipe to generate the requested element.

Fun implementation detail of lazy seqs is that Clojure chunks them. Whenever Clojure has to realize one element, it also pre-emptively realizes 31 next elements for future performance.

You can also generate infinite sequences with lazy seqs.

Collection Abstraction - while the sequence abstraction is about operating on members individually, the collection abstraction is about the data structure as a whole.

(into [0] [1])
; => [0 1]

(conj [0] 1)
; => [0 1]

conj takes a rest parameter vs into takes a seqable data structure

Function Functions

apply explodes a seqable data structure so it can be passed to a function that expects a rest parameter

(apply max [0 1 2])
; => 2

; max expects a number of args, not a seqable data structure
(apply max [0 1 2]) is equivalent to (max 0 1 2)

partial takes a function and any number of args and returns a new function. it forms a closure with the originally supplied args and you can call this function with new args

(def add10 (partial + 10))
(add10 3)
; => 13

(add10 5)
; => 15

(def add-missing-elements
(partial conj ["water" "earth" "air"]))

(add-missing-elements "fire")
; => ["water" "earth" "air" "fire"]

complement - takes a fn f and returns a fn that takes the same args as f, has the same effects, but returns the opposite truth value

(defn iseven?
    [n]
    (prn 'str "Given number is: " n')
    (= (rem n 2) 0))

(def isodd? (complement iseven?))

(iseven? 20) ; => true
(isodd? 21) ; => true

Chapter Exercises

  1. Turn the result of your glitter filter into a list of names.
(defn glitter-filter-list
  [records]
  (map :name records))
  1. Write a function, append, which will append a new suspect to your list of suspects.
(defn append
  [suspect-list name glitter]
  (conj suspect-list {:name name, :glitter glitter}))

(def sus-list (mapify (parse (slurp filename))))
(append sus-list "Jim Moriarty" "300")
  1. Write a function, validate, which will check that :name and :glitter-index are present when you append. The validate function should accept two arguments: a map of keywords to validating functions, similar to conversions, and the record to be validated.
(defn validate
  [kw-validate-fn record]
  (apply = true (map (fn [k] (contains? (keys kw-validate-fn) (key k))) record)))
  1. Write a function that will take your list of maps and convert it back to a CSV string. You’ll need to use the clojure.string/join function.
(defn collapse-map
  [records]
  (clojure.string/join "\n" (map (fn [r] (clojure.string/join "," (map str (vals r)))) records)))

Athens Debugging

While colloborating on the fix indent and unindent issue with my Team Seneca cohort mates, I wanted to figure out how to debug the internal state of Athens at a given moment.

Approaches that I considered -

Understanding how to use re-frame 10x even a little bit ended up being quite helpful since that led to solving the unindent part of the issue.

Solving the Unindent Issue

The unindent issue was quite interesting. At a first glance, all we have to do is prevent the default shift+tab behaviour on a node if it is already at the top level. However, the issue has more depth - Athens allows you to click on a node, which opens up the node in a new page. Now the node and it’s children are presented in a different context. In this new page, a user could freely keep unindenting a node until it disappeared from the current context (the node still existed, just as an uncle to the current node). The expected behavior is that we shouldn’t be able to un-indent a node beyond that context that it is being rendered in.

My cohort-mate and I hit upon the idea that the :current-route object which contains the state for the current route of the page would give us an idea of which node was the root node in the current context. This turned out to be a winning approach!

We could then compare this root context node with the parent node of the block that the user is trying to unindent and if they’re the same, we shouldn’t let the unindent happen.

The code itself turned out to be fairly trivial. The only challenging part was figuring out how to access the :current-route object within rfdb inside the :unindent event handler. The good thing about re-frame event handlers is that you can access dbs as Co-Effects.

(reg-event-fx
  :unindent
  (fn [{rfdb :db} [_ uid]] ;; Pass in the reframe db as a cofx to the :unindent event handler
    (let [context-root (get-in rfdb [:current-route :path-params :id]))])
   (unindent uid context-root))

Now, we can make the comparison between context-root and parent -

(defn unindent
  [uid context-root]
  (let [parent (db/get-parent [:block/uid uid])
        grandpa (db/get-parent (:db/id parent))
        new-block {:block/uid uid :block/order (inc (:block/order parent))}
        reindex-grandpa (->> (inc-after (:db/id grandpa) (:block/order parent))
                             (concat [new-block]))]
    (if (= (:block/uid parent) context-root) ; -> compare parent node id of current node
      {}                                     ; -> with context-root
      (when (and parent grandpa)
        {:transact! [[:db/retract (:db/id parent) :block/children [:block/uid uid]]
                     {:db/id (:db/id grandpa) :block/children reindex-grandpa}]}))))

After testing that this solved the issue, I embarked on the journey of combining all the work my cohort mates and I had done on this issue into a series of commits we could submit as a PR. Of course, in this process, I encountered the dreaded merge conflict which I had to resolve via a quick git rebase.

Finally, we were able to submit the PR!!

Now to wait and hopefully get good feedback on the code.

Takeaways

I ended up spending a little bit more time today on just futzing with CLJS tooling and figuring out how to debug code. While not explicitly productive, I still think it’s useful info to tuck away for a rainy day.

It feels immensely satisfying to bring an issue to conclusion especially by working with others! I hope this PR will be the first of many!

I think I’ll spend tomorrow reading Chapter 5 of Brave Clojure and finishing up the rest of LearnDataLog.

Today’s tally -