Today is the beginning of week 3 of ClojureFam (how time flies...)
map
is an example of a lazy function. It returns almost instantly and only when the data is accessed, does it apply the function it took as an argument. Every unrealized element has the recipe for realizing each element. Once an element is realized, it doesn't need to be realized again to be accessed.
However, Clojure chunks its unrealized elements when you try to access them. So if you try to access the first n elements of sequence, it will realize a bunch more as well.
To create infinite sequences, we use repeat
and repeatedly
(the latter taking a function).
into
will turn the result of a seq function (it returns a sequence, a list) into another data structure. So for instance it can turn the result of a map taking a set back into a set.
(into {} (map identity {:sunlight-reaction "Glitter!"}))
; => {:sunlight-reaction "Glitter!"}
One more thing to note is that the first collection doesn't have to be empty.
conj
takes a vector, and one or several new elements to append at the end. It's very similar to into
but takes rest parameter instead.
One thing I've wanted to do since I started learning clojure is taking a sequence and pass it to a function that takes rest arguments, like conj
we just looked into. And the way to do it is with...
apply
does just that: (apply max [0 1 2])
=> 2
partial
I am already fine with, it takes a function and arguments then returns a functions which can take arguments, and that new function will take the new arguments alongside the originally supplied ones:
(def add10 (partial + 10))
(add10 3)
; => 13
(add10 5)
; => 15
complement
is new to me. It simply inverts a predicate function so for instance you could do (def non-nil? (complement nil?))
.
- Load core.clj with
(ns fwpd.core)
, reload with(use 'fwpd.core :reload)
(defn extract-names
[records]
(map #(:name %) records))
(defn append
[records new-suspect]
(conj records new-suspect))
3. 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
[keyword-to-validating-function record]
(apply = true (map (fn [key] ((key keyword-to-validating-function) (key record))) (keys keyword-to-validating-function))))
4. 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 convert-to-csv
[records]
(clojure.string/join "\n" (map (fn [record] (clojure.string/join "," (map str (vals record)))) records)))
The content of the Chapter 4 of Clojure for the Brave and True were not new in itself (barely any new function for instance), but it went much deeper into why Clojure behaves the way it is, and why the core functions are what they are. I may still not be getting Clojure itself (why Clojure and not JavaScript for instance), but at least I am starting to understand some of the abstractions. Programming by abstractions definitely has a big appeal, forget about the nitty gritty of the implementation, and focus on what you want to achieve (for instance with the sequence abstraction).
Another takeaway that from today were the exercises: while the first two were super simple, the last two, while not being too complicated were challenging and interesting in the sense that they were mirroring things that I often end up scripting in JavaScript. More precisely string parsing and manipulating. For instance I recently started working on a tool that extracts specific items from a Roam Research backup. This is definitely something that I am starting to understand how to do in Clojure.