Types, Exercises and Functional Enlightenment
Jun 23, 2020
Contents
Expectations
Yesterday, I set some expectations for myself regarding what I would learn today. And after spending a few hours vacillating between CFGU and Clojure for the Brave and True, I’ve chosen CFGU as my current learning material. Brave and True will come later once I get familiarity with the basics of Clojure.
I also expected myself to be more productive today after all the effort I spent setting a good dev environment with Emacs and I have to say that that’s panned out as well. I do feel quite comfortable interacting with the CIDER REPL and I can feel my old muscle memory for Emacs keybinds coming back.
What I learned
I spent most of today on Chapter 2 of CFGU which covers basic Clojure types and some commonly used functions.
Some interesting observations:
- Clojure will fail rather than perform unintuitive type coercions
- Clojure aims to preserve as much information as possible during math operations. For eg.
(+ 2 3.0)
gives us5.0
since the Double type hold more info than a Long. - Most math functions are multi-arity functions and can be applied from 1 to N inputs
>=
and<=
allow you to test multiple inputs for increasing or decreasing monotonicity- The
str
fn does double work as a cast to string function and a concatenation function
Using Regexes is also surprisingly simple in Clojure. I like that CFGU tackles this topic early on, since searching for text in strings is a pretty basic operation and everyone who’s learning to program know how to do it effectively.
4Clojure
Unlike my usual method of learning, where I tend to focus more on theory initially, this time I’m diving head on into practical problem solving. I sped through the elementary difficulty problems on 4Clojure. Some of the problems actually did end up making me think about how to solve them in idiomatic Clojure. Instead of using imperative constructs, I tried to use more recursion and functional composition techniques to solve them.
There were 2 problems that led to BIG LEARNING MOMENTS.
No: 21
Write a function which returns the Nth element from a sequence.
(= (__ '(4 5 6 7) 2) 6)
My first instinct was to use a for-loop, maintain a counter and when that counter was equal to N, return the element at the current index. However, I haven’t really come across for loops in Clojure so far. And I wanted to solve it without reaching into that Imperative Toolbox. So I tried using Recursion. This was my final solution:
(defn newnth [coll ind]
(if (= 0 ind)
(first coll)
(newnth (rest coll) (- ind 1))
Pretty classic solution I would say. When I discussed this with my fellow Cohort members, their solutions were eye-opening.
One was - #((vec %1) %2)
which uses the vec
function to convert the collection into a vector and then access the index directly.
Vectors unlike Lists are array indexed and we can simply get the element at an index.
Another solution was - (fn [xs n] (first (drop n xs)))
. This uses the drop
function which returns a lazy sequence of all but the first n items in coll.
Lazy Sequences are a functional programming concept which effectively allow you to chain multiple transformations on a sequence and only perform
the data intensive computation when needed. This is much more efficient than a strictly imperative model where the transformations are done immediately.
No: 22
Write a function which returns the total number of elements in a sequence.
(= (__ '(1 2 3 3 1)) 5)
(= (__ [[1 2] [3 4] [5 6]]) 3)
(= (__ "Hello World") 11)
This one has some interesting constraints. For one, the inputs can be lists, vectors or strings.
After that little functional composition revelation I had, I decided to leverage some pretty powerful functions, namely map
and reduce
.
No FP language can be complete without these two.
My original solution:
(fn [coll]
(reduce + (map #(if % 1 0) coll)))
Mapping the #(if % 1 0)
over a sequence yields a list of 1s with the same length as the original sequence. This list of 1s can then be reduced and summed up
giving us the length of the sequence. I was a little disappointed in myself for using the #(if % 1 0)
since I wanted to avoid any control flow mechanisms.
A cohort member used (fn [x] 1)
in place of #(if % 1 0)
which I think is a better idiomatic choice. (fn [x] 1)
simply returns 1 for any input which is
exactly what I wanted to do!
Takeaways
I’m honestly pleasantly surprised how useful it is to be able to bounce ideas off of other people. Being a part of Team Seneca and getting to talk about Clojure and problems is genuinely an honor. I’ve learned quite a lot of already!
Let’s end this day with a tally of what I completed:
- 20 4Clojure Problems.
- Chapter 2 of CFGU
- Reading Part 1 of My love letter to Clojure
#(prn "Fin Day 2! See ya tomorrow")