Functional Programming
Functions are first-class values. Pass them, return them, compose them.
map, filter, reduce
The core trio:
(map (fn [x] (* x x)) [1 2 3 4 5]) ; [1 4 9 16 25]
(filter even? [1 2 3 4 5 6]) ; [2 4 6]
(reduce + 0 [1 2 3 4 5]) ; 15
(reduce str "" ["a" "b" "c"]) ; "abc"
map and filter accept any callable — named function, anonymous function, or keyword.
Keywords as Functions
A bare keyword acts as a field accessor — useful with map:
(def movies
[{:title "Arrival" :year 2016 :rating 7.9}
{:title "Heat" :year 1995 :rating 8.3}
{:title "Parasite" :year 2019 :rating 8.5}])
(map :title movies) ; ["Arrival" "Heat" "Parasite"]
(map :rating movies) ; [7.9 8.3 8.5]
partial
partial fixes some arguments, returning a new function for the rest:
(def add5 (partial + 5))
(add5 10) ; 15
(add5 20) ; 25
(def short? (partial shorter-than? 120))
(filter short? movies)
comp
comp chains functions right-to-left:
(def loud-greet (comp str/upper (partial str "Hello, ")))
(loud-greet "alice") ; "HELLO, ALICE"
complement
complement negates a predicate:
(def odd? (complement even?))
(filter odd? [1 2 3 4 5]) ; [1 3 5]
juxt
juxt applies multiple functions to the same value, returning a vector of results:
((juxt :title :year :rating) (first movies))
; ["Arrival" 2016 7.9]
(map (juxt :title :rating) movies)
; [["Arrival" 7.9] ["Heat" 8.3] ["Parasite" 8.5]]
apply
apply calls a function with a collection as its argument list:
(apply + [1 2 3 4 5]) ; 15
(apply str ["a" "b" "c"]) ; "abc"
(apply max [3 1 4 1 5 9]) ; 9
Advanced Collection Functions
; Sort by a derived key
(sort-by :rating movies)
(sort-by (comp - :rating) movies) ; descending
; Group into a map
(group-by :year movies)
; {2016 [{...}] 1995 [{...}] 2019 [{...}]}
; Count occurrences
(frequencies [:a :b :a :c :b :a])
; {:a 3 :b 2 :c 1}
; Partition into chunks
(partition 2 [1 2 3 4 5 6])
; [[1 2] [3 4] [5 6]]
; Take/drop while a condition holds
(take-while even? [2 4 6 1 2]) ; [2 4 6]
(drop-while even? [2 4 6 1 2]) ; [1 2]
; like map but filters nils
(keep (fn [x] (when (even? x) (* x 10))) [1 2 3 4])
; [20 40]
Pipelines Without Threading Macros
Chain operations with intermediate let bindings:
(defn top-picks [catalog any watched any] -> []any
(let [unseen (filter (fn [m] (not (contains? watched (:title m)))) catalog)
ranked (sort-by (fn [m] (- (float64 (:rating m)))) unseen)
top (take 3 ranked)]
(map :title top)))
Or with threading macros (if you prefer):
(defn top-picks [catalog any watched any] -> []any
(->> catalog
(filter (fn [m] (not (contains? watched (:title m)))))
(sort-by (fn [m] (- (float64 (:rating m)))))
(take 3)
(map :title)))
->> threads each result as the last argument. -> threads as the first argument.
When the threaded value needs to land in a different position from one step to the next, as-> binds it to a name you choose:
(as-> {} $
(assoc $ "k" 1)
(dissoc $ "old")
(merge $ {"done" true}))
Each form may reference $ anywhere — first arg, last arg, or buried in the middle.
Nil-Safe and Conditional Threading
some-> and some->> thread like ->/->> but short-circuit to nil the
moment any step yields nil — they collapse a stack of nil-guards into one line:
; Returns nil if any key is missing, instead of panicking on a nil map
(some-> req
(get "user")
(get "email")
str/lower)
(some->> orders
(filter paid?)
(map :total)
(reduce +))
cond-> and cond->> thread the value only through the forms whose paired test
is truthy. Unlike cond, every test is checked, so it is ideal for building a
value up from optional pieces:
(defn build-query [base any opts any] -> any
(cond-> base
(:active opts) (assoc :status "active")
(:since opts) (assoc :since (:since opts))
(:limit opts) (assoc :limit (:limit opts))))
cond->> is the same but threads as the last argument of each chosen form.
Calling Function Values
A function stored in an any-typed binding — the result of comp, juxt,
partial, or a map lookup — is callable directly, no apply needed:
(defn run-twice [f any x any] -> any
(f (f x)))
(run-twice (partial + 3) 10) ; 16
(let [handlers {:greet (fn [n] (str "hi " n))}]
((:greet handlers) "Ada")) ; "hi Ada"
Bare Functions as Arguments
A single-argument named or core function can be passed straight into a higher-order function — the compiler wraps it to fit:
(map str/upper ["a" "b"]) ; ["A" "B"]
(filter str/blank? lines)
(map :title movies)
Multi-argument functions like + still need an explicit wrapper in these
positions: (reduce (fn [a b] (+ a b)) 0 nums). If a function's shape doesn't
fit the slot, the compiler reports it at the call site and suggests the wrapper.
Debugging Pipelines
pp pretty-prints a value (sorted map keys, indented nesting) and returns it unchanged, so it drops into any expression without disturbing the result:
(pp {:b 2 :a 1}) ; prints the map, returns it
tap-> and tap->> are ->/->> that pretty-print each intermediate stage — a pipeline you can watch:
(tap-> 5 (+ 3) (* 2)) ; prints 5, then 8, then 16; returns 16
time-it evaluates an expression, prints how long it took, and returns its value:
(time-it (expensive-computation))
All three pass their value through untouched, so you can wrap a subexpression to inspect it and remove the wrapper later without changing behavior.