The introductory statement above just about says it all. Here's a concrete example of that principle applied to solving problem 22 of "Project Euler". (Make sure you read the question description to understand the following code. https://projecteuler.net/problem=22)
We start with the input:
(def input ["MARY", "PATRICIA", "LINDA", ...
We test-drive a function for calculating the 'alphabetic' score:
(defn alpha-score [word] (->> (string/lower-case word) (map #(inc (- (int %) (int \a)))) (apply +)))
This function does the following:
(it "computes alphabetic score" (should= 6 (alpha-score "abc")) (should= 53 (alpha-score "COLIN")))
We test-drive a function for calculating the 'name' score:
(defn name-score [rank word] (* rank (alpha-score word)))
Supposing that the name "COLIN" is the 938th name in the list:
(it "computes name score" (should= 49714 (name-score 938 "COLIN")))
Now, to solve the problem we must:
Here's a literal implementation of the above steps:
(defn solve-1  (let [indices (range 1 (inc (count input)))] (as-> #_"step 1" (sort input) $ #_"step 2" (interleave indices $) #_"step 3" (partition 2 $) #_"step 4" (map #(name-score (first %1) (second %1)) $) #_"step 5" (apply + $))))
It seems that steps 2 and 3 are really just a mapping of two collections over a function that receives two arguments...WHICH IS WHAT THE MAP FUNCTION ALREADY DOES!
Most of the time when we use
map we are mapping a single collection over a function that receives that argument. But
map is much more flexible in that it expects 'n' collections and a function that receives 'n' arguments.
Here's a more succinct implementation which makes use of this knowledge:
(defn solve-2  (let [indices (range 1 (inc (count input)))] (as-> #_"step 1 " (sort input) $ #_"steps 2-4" (map name-score indices $) #_"step 5 " (apply + $))))
(Of course, we could use
->> ('thread-last') instead of
as-> ('thread-as'), but I like how 'thread-as' overtly displays both collections being passed to the
map function in this case.)