Skip to content
AgileLogics
6 min read

Higher-Order Functions

Prerequisites: Functions, let and local bindings

A higher-order function takes or returns functions. Clojure's core library has a set of small ones that combine into almost anything.

partial, comp, and apply

(def add-tax (partial * 1.2))            ; fix the first argument
(def greet (partial str "Hello, "))
(println (add-tax 100) (greet "Ada"))

(def shout (comp clojure.string/upper-case #(str % "!")))
(println (shout "hey"))                   ; right to left: add "!", then upper-case

(def slug (comp #(clojure.string/replace % " " "-") clojure.string/lower-case clojure.string/trim))
(println (slug "  Hello Clojure World "))

(println (apply max [3 9 2]))
(println (apply str "a" "b" ["c" "d"]))
120.0 Hello, Ada
HEY!
hello-clojure-world
9
abcd
  • partial returns a function with some arguments already supplied.
  • comp composes functions right to left: ((comp f g) x) is (f (g x)).
  • apply spreads a collection into arguments (after any fixed ones).

juxt, complement, and friends

(println ((juxt :name :age) {:name "Ada" :age 36}))
(println (map (juxt identity #(* % %)) [1 2 3]))

(def not-blank? (complement clojure.string/blank?))
(println (filter not-blank? ["a" "" "  " "b"]))

(def valid-age? (every-pred integer? #(<= 0 % 150)))
(println (map valid-age? [36 -1 200 "x"]))

(def contact (some-fn :email :phone))
(println (contact {:phone "555"}) (contact {}))

(println (map (constantly 0) [1 2 3]))
(println ((fnil + 0) nil 5))
[Ada 36]
([1 1] [2 4] [3 9])
(a b)
(true false false false)
555 nil
(0 0 0)
5
  • juxt calls several functions on the same input and returns a vector of results — great for sorting by several keys.
  • complement negates a predicate; every-pred and some-fn combine predicates.
  • constantly ignores its arguments; fnil replaces nil arguments with defaults.

Returning functions

(defn make-counter []
  (let [count (atom 0)]
    (fn [] (swap! count inc))))

(def next-id (make-counter))
(println (next-id) (next-id) (next-id))

(defn logging [f label]
  (fn [& args]
    (let [result (apply f args)]
      (println label args "=>" result)
      result)))

(def logged-add (logging + "add"))
(logged-add 2 3)
1 2 3
add (2 3) => 5

A function that wraps another to add behavior (logging, retries, caching) is called a decorator or middleware — Ring web applications (Level 3) are built this way. (atom is covered in the Atoms lesson.)

memoize

(def slow-square
  (memoize (fn [n] (Thread/sleep 100) (* n n))))

(let [start (System/nanoTime)]
  (dotimes [_ 5] (slow-square 9))
  (println "5 calls took less than 300 ms:"
           (< (/ (- (System/nanoTime) start) 1e6) 300)))
5 calls took less than 300 ms: true

Only the first call slept; the other four came from the cache.