This is a rest day after the holiday, I expect to do as much as I can of Chapter 4 of Brave Clojure without setting expectations too high.
Programming by abstractions means that a certain function is not tailored to a specific data structure. It is about the operation, not about the underlying datastructure.
For example, the battery abstraction includes the operation “connect a conducting medium to its anode and cathode,” and the operation’s output is electrical current. It doesn’t matter if the battery is made out of lithium or out of potatoes. It’s a battery as long as it responds to the set of operations that define battery.
In Clojure, if cons
, first
and rest
work on a datastructure then it's a seq and any seq funcion will work on it. Potentially it is because these functions are implemented with cons
, first
and rest
.
So I tried to re-implement map
in terms of these three functions:
(defn my-map
[f coll]
(if (nil? coll)
nil
(cons (f (first coll) (map (rest coll))))))
After testing it out, I looked up the code, and indeed, map
is made with these three functions: https://github.com/clojure/clojure/blob/clojure-1.10.1/src/clj/clojure/core.clj#L2727
- It is possible to pass more than one sequence to
map
. - You can also pass it a collection of functions, here is a great example:
(def sum #(reduce + %))
(def avg #(/ (sum %) (count %)))
(defn stats
[numbers]
(map #(% numbers) [sum count avg]))
(stats [3 4 10])
; => (17 3 17/3)
(stats [80 1 44 13 6])
; => (144 5 144/5)
- Or to retrieve the the value associated with a keyword. Again here is an example from Brave Clojure:
(def identities
[{:alias "Batman" :real "Bruce Wayne"}
{:alias "Spider-Man" :real "Peter Parker"}
{:alias "Santa" :real "Your mom"}
{:alias "Easter Bunny" :real "Your dad"}])
(map :real identities)
; => ("Bruce Wayne" "Peter Parker" "Your mom" "Your dad")
- It's possible to use
reduce
to transform a map's value (same keys but updated values):
(reduce (fn [new-map [key val]]
(assoc new-map key (inc val)))
{}
{:max 30 :min 10})
; => {:max 31, :min 11}```
- You can also use `reduce` to filter out some values in a map:
```clojure
(reduce (fn [new-map [key val]]
(if (> val 4)
(assoc new-map key val)
new-map))
{}
{:human 4.1
:critter 3.9})
; => {:human 4.1}
- Both take a sequence and a number and will return either the first n elements of the sequence (for the former), or everything but the first n elements (for the latter).
- There is also
take-while
anddrop-while
that will take a predicate function instead of the number. However, in this case I would normally usefilter
. The advantage oftake-while
anddrop-while
is that it doesn't process all your data. It stops when the predicate is not satisfied anymore.
You can get some to return the actual value of a function if you transform the predicate function by wrapping it in an and
like this: (some #(and (> (:critter %) 3) %) food-journal)
.
sort-by
takes a function to sort, so you could use count
for instance
Today I read and took notes for a little more than half of the 4th Chapter of Clojure for the Brave and True. It goes further than Clojure for the Ground Up on the same topics. Going through it now makes complete sense as I have the basics down and I can pick up why things are the way they are, and I hope, why Clojure makes sense.