Browse Learn Clojure Foundations as a Java Developer

Functions as Values

Use first-class and higher-order functions to pass behavior directly, replace small strategy interfaces, build reusable pipelines, and keep Clojure APIs compact.

In Clojure, functions are ordinary values. You can pass them as arguments, return them, store them in maps, and compose them into pipelines.

First-class function: A function that can be treated like any other value.

Higher-order function: A function that accepts another function, returns one, or both.

For Java engineers, the practical shift is that reusable behavior often becomes a function argument rather than an interface implementation.

Strategy Objects Become Function Arguments

 1(defn count-matching [pred xs]
 2  (count (filter pred xs)))
 3
 4(count-matching even? [1 2 3 4])
 5;; => 2
 6
 7(count-matching #(> (:order/total-cents %) 5000)
 8                [{:order/id 1 :order/total-cents 3000}
 9                 {:order/id 2 :order/total-cents 9000}])
10;; => 1

In Java, this might be a Predicate<T> or strategy interface. In Clojure, the function value is the strategy.

Closures Capture Configuration

1(defn minimum-total [cents]
2  (fn [order]
3    (>= (:order/total-cents order) cents)))
4
5(filter (minimum-total 5000)
6        [{:order/id 1 :order/total-cents 3000}
7         {:order/id 2 :order/total-cents 9000}])
8;; => ({:order/id 2, :order/total-cents 9000})

The returned function remembers cents. That replaces a lot of small “configuration holder plus method” classes.

Higher-Order Functions Are Everyday Vocabulary

Function Role Java comparison
map Transform each element Stream map
filter Keep matching values Stream filter
remove Drop matching values Negated predicate filter
reduce Fold many values into one Stream reduction or loop accumulator
sort-by Sort using a key function Comparator based on extracted key

Example:

 1(require '[clojure.string :as str])
 2
 3(defn normalize-email [email]
 4  (-> email str/trim str/lower-case))
 5
 6(defn active-email-list [users]
 7  (->> users
 8       (filter :user/active?)
 9       (map :user/email)
10       (map normalize-email)
11       (into [])))

The code describes data flow. Behavior is passed as named functions, keywords, or small anonymous functions.

Functions Can Live In Maps

1(def renderers
2  {:json  (fn [rows] {:content-type "application/json" :body rows})
3   :count (fn [rows] {:content-type "text/plain" :body (str (count rows))})})
4
5((get renderers :count) [{:id 1} {:id 2}])
6;; => {:content-type "text/plain", :body "2"}

For small explicit dispatch tables, a map of functions can be clearer than a class hierarchy. Use it when the set of cases is visible and stable enough to review directly.

When Higher-Order Code Goes Too Far

Function-heavy code can become unreadable if every rule is anonymous and nested. Keep these review rules:

Smell Correction
Large anonymous function Give it a name
Several nested higher-order calls Consider ->> or helper functions
Behavior hidden in a data map nobody can find Move it near the call site or namespace it clearly
Clever abstraction with one use Inline it until variation is real

The goal is direct behavior, not abstraction density.

Quiz: Functions as Values

### What makes a function first-class in Clojure? - [x] It can be passed, returned, stored, and called as a value. - [ ] It must be defined before all data. - [ ] It cannot close over values. - [ ] It can only be used in `map`. > **Explanation:** First-class functions are ordinary values. That is why Clojure can pass behavior directly into APIs. ### What does this evaluate to? ```clojure (defn apply-twice [f x] (f (f x))) (apply-twice inc 5) ``` - [x] `7` - [ ] `6` - [ ] `5` - [ ] It throws because `inc` is not a value. > **Explanation:** `inc` is passed as a function value and called twice: `5` becomes `6`, then `7`. ### When is a map of functions a good fit? - [x] When a small explicit set of behaviors can be selected by a key. - [ ] When every domain concept needs inheritance. - [ ] When mutation must be hidden. - [ ] When functions cannot accept arguments. > **Explanation:** Function maps are useful for compact, visible dispatch when the set of cases is clear.
Revised on Saturday, May 23, 2026