Skip to content

Latest commit

 

History

History
119 lines (88 loc) · 5.27 KB

2020-07-01.md

File metadata and controls

119 lines (88 loc) · 5.27 KB

Learning Clojure in Public - Week 2 Day 3 (10/35)

Expectations

Today the expectation was to finish Clojure from the Ground Up. There is no more chapter after the debugging one. Usually I read and take notes for half a chapter per day but this one seems not heavy on new knowledge and code so I hope to complete it in one evening.

If I have more time I hope to make some progress with my 4clojure problems, after all I just got to my 60 problem milestone, and cannot stop here!

If I get to 100 before the end of ClojureFam I plan on revisiting some earlier problems and attempting to solve them with different (better) approaches. For instance, not use a recursion when it's possible.

What I learned

Debugging in Clojure

Since I started reading this book, I have felt ill-equipped to debug my code, so what I learned in this chapter has been very interesting. It starts with understanding the error messages and being able to analyze the stack traces as well. The first example given by the book is:

(ns scratch.debugging)

(defn bake
  "Bakes a cake for a certain amount of time, returning a cake with a new
  :tastiness level."
  [pie temp time]
  (assoc pie :tastiness
         (condp (* temp time) <
           400 :burned
           350 :perfect
           300 :soggy)))

And then the function call is:

user=> (bake {:flavor :blackberry} 375 10.25)
ClassCastException java.lang.Double cannot be cast to clojure.lang.IFn  scratch.debugging/bake (debugging.clj:8)

Without looking at the stack trace we can see it must have to do with the last argument of the function, time (= 10.25 which is a Double). Then there may be an issue with condp because it was expecting a function (IFn) and it got a Double. This is probably not what condp was expecting. It's expecting a binary predicate (a function that returns true or false). So function first, then two arguments:

(condp < (* temp time)
           400 :burned
           350 :perfect
           300 :soggy)

When we're looking at stack traces like this, for instance when we get a NullPointerException (when a function is expecting a value and receives nil), it's a little bit annoying to not be able to understand how that value got there. Usually, in JavaScript, I would put a (conditional) breakpoint before the crash so I could see where this value is coming from. The author offers to use prn in a reduce for instance. It's helpful of course, but I would like more.

It's also interesting that I am getting more and more into types (with TypeScript) and now I am once again looking into a dynamically typed language. You end up relying a lot on types...

What I should remember though is the spy function he passes to the macro:

(defn spy
  [& args]
  (apply prn args)
  (last args))

(let [segments (->> photos
                    ; Convert photos to frame dimensions
                    (map (partial frame mat-width))
                    (spy :frames)
                    ; Convert frames to segments
                    (mapcat perimeter))]
    ```
### Interesting 4clojure problem: 63
Given a function f and a sequence s, write a function which returns a map. The keys should be the values of f applied to each item in s. The value at each key should be a vector of corresponding items in the order they appear in s.

For this problem it is obviously not allowed to use `group-by`. This does look like a reduce so let's start with this. To this reduce we will pass a function, a start value `{}` and the collection.
```clojure
(fn group-sequence [f coll]
  (reduce
   (fn [] ())
   {} coll))

We are going to use the result of each element by that function f, so let's compute the result in a let:

(fn group-sequence [f coll]
  (reduce
   (fn [m e] (let [result (f e)]
               ()))
   {} coll))

That function within the reduce needs to append to a vector of existing values after that keyword, so for that we will use assoc:

(fn group-sequence [f coll]
  (reduce
   (fn [m e] (let [result (f e)]
               (assoc m result e)))
   {} coll))

However, we are not storing the result, but appending it to the vector, so we use get to retrieve the current value:

(fn group-sequence [f coll]
  (reduce
   (fn [m e] (let [result (f e)]
               (assoc m result (conj (vec (get m result)) e))))
   {} coll))

Takeaways

Today I learned to utilize the stack trace to troubleshoot my issues. While it's I'm welcoming this knowledge I will still look into setting up a debugger potentially (maybe Calva and VS Code can work together?).

This was the last chapter of the book. All in all it was a great experience and quite easy to understand, take notes from the material. The chapters with long code snippets were harder to navigate as it was much easier to introduce a bug (and the rest relies on earlier code) and get you stuck for the rest of the chapter.

I also completed another 8 4clojure problems. I'm over 2/3 of my goal, but I do expect the difficulty to be significantly higher when I get to the medium ones, so I'm enjoying this while it lasts.

Let's end the day with a tally of what I completed:

  • Chapter 10 of Clojure From the Ground Up, which was the last chapter of the book!
  • 8 4clojure problems