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
partialreturns a function with some arguments already supplied.compcomposes functions right to left:((comp f g) x)is(f (g x)).applyspreads 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
juxtcalls several functions on the same input and returns a vector of results — great for sorting by several keys.complementnegates a predicate;every-predandsome-fncombine predicates.constantlyignores its arguments;fnilreplacesnilarguments 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.