The expectation today was cover chapter 5 of Brave Clojure. My cohort-mate and were also on the final stretch for our collaborative Athens issue so I wanted to cover some of that as well.
- A pure function always returns the same result given the same argument(s). This is called referential transparency.
- A pure function cannot cause any side effects (changes that are observable outside the function itself). With pure function you pass the result of a function as argument to another. It's called function composition. Functional programming encourages you to build more complex functions by combining simpler functions.
(defn sum
([vals] (sum vals 0))
([vals accumulating-total]
(if (empty? vals)
accumulating-total
(sum (rest vals) (+ (first vals) accumulating-total)))))
A more efficient is to do this with recur
:
(defn sum
([vals]
(sum vals 0))
([vals accumulating-total]
(if (empty? vals)
accumulating-total
(recur (rest vals) (+ (first vals) accumulating-total)))))
comp
takes function and returns a new anonymous function that is the function composition of them. ((comp inc *) 2 3)
, first it multiplies and then takes the result and increases it.
Here is another example to access nested maps:
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
memoize
saves the arguments and the return of a function, so when you evaluate the function with the same arguments again the result is returned immediately.
(def memo-sleepy-identity (memoize sleepy-identity))
(memo-sleepy-identity "Mr. Fantastico")
; => "Mr. Fantastico" after 1 second
(memo-sleepy-identity "Mr. Fantastico")
; => "Mr. Fantastico" immediately
Exercise 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.
(defn attr
[attribute]
(comp attribute :attributes))
(defn my-comp
[& functions]
(fn [& args]
(reduce (fn [arg f] (f arg)) (apply (last functions) args) (reverse functions))))
This one was quite challenging, and I'm happy I found a solution. It's for sure different from the ones already posted online.
Exercise 3: 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] val]
(if (nil? ks) (assoc m k val) (assoc m k (my-assoc-in (get m k {}) ks val))))
user=>
(def foo {:user {:bar 1}})
#'user/foo
user=>
(update-in foo [:user :bar] inc)
{:user {:bar 2}}
(def foo {:user {:bar 1}})
(defn my-update-in
[m [k & ks] f]
(if (nil? ks) (do (prn "hello") (assoc m k (f (get m k 1)))) (assoc m k (my-assoc-in (get m k {}) ks f))))
(my-update-in foo [:user :bar] inc)
This one doesn't work just yet, it's almost there but instead of evaluating the function with arguments it just returns the function itself.
Today I am looking at the unindent problem, part of the same issue. When on a page, the user cannot unindent a node that is already at room level. However, when the user has "entered" a node (that's when you click on the bullet and zoom into a node that becomes a page), that's when it is possible to unindent it (it goes back to the page, which makes sense but is counterintuitive for the user).
I originally thought it was the enter
function that was responsible for getting into the node, but this is actually for pressing... Enter.
Our next lead was :current-route
which was closer. Eventually we found a way to pass the navigated page down to the unindent event which we can then compare to the parent node of the current node and figure out if we can indent or not.
(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)
{}
(when (and parent grandpa)
{:transact! [[:db/retract (:db/id parent) :block/children [:block/uid uid]]
{:db/id (:db/id grandpa) :block/children reindex-grandpa}]}))))
(reg-event-fx
:unindent
(fn [{rfdb :db} [_ uid]] ;; Pass in the reframe db as a cofx to the :unindent event handler
(unindent uid (get-in rfdb [:current-route :path-params :id]))))
I'm glad the brave clojure chapter was lighter than the others, so I was able to spend more time on troubleshooting our issue. We now even have an open PR for it!
The exercises were also challenging and I am glad I completed them. I got to compare my solutions with others posted on GitHub and mine are different, no one came up with super smart solutions, which is reassuring.
Tomorrow I will spend some time looking into the existing issues and see if I can pick one up by myself.
Let's end the day with a tally of what I completed:
- Completed Chapter 5 of Clojure for the Brave and True
- Also did the 5 exercises in that same chapter
- FInally solved the last problems in our first collaborative issue in Athens, and opened the PR