Browse Clojure Foundations for Java Developers

Writing Custom Functions That Accept Functions

Learn when it is worth writing your own function-taking functions in Clojure, how to design them cleanly, and how to avoid thin wrappers that add no value.

Built-in higher-order functions such as map, filter, and reduce get you a long way.

But eventually you will want to capture a domain-specific pattern, not just a raw collection operation. That is when writing your own function-taking function starts to make sense.

The important question is not “Can I accept a function here?”

The important question is:

does passing a function make this API clearer by isolating the part that actually varies?

A Good Reason To Write One

Suppose you often need to transform only the orders that match a rule, while leaving the others unchanged.

1(defn transform-matching [pred xf coll]
2  (mapv (fn [item]
3          (if (pred item)
4            (xf item)
5            item))
6        coll))

Now you can reuse the same pattern with different rules and transformations:

1(defn expedited? [order]
2  (:order/expedited? order))
3
4(defn mark-priority [order]
5  (assoc order :order/priority :high))
6
7(transform-matching expedited? mark-priority orders)

This abstraction earns its keep because it names a real pattern:

  • keep the collection shape
  • change only matching items
  • let the caller decide both the rule and the transformation

A Bad Reason To Write One

This, by contrast, is usually not worth introducing:

1(defn apply-to-all [f coll]
2  (map f coll))

It adds no domain meaning and teaches the reader nothing that map did not already say clearly.

That distinction matters. A custom higher-order function should capture a reusable pattern, not just rename a core function.

Design Rules That Help

Rule Why it matters
Name the varying behavior clearly pred, xf, score-fn, and key-fn communicate the contract quickly
Put collection or main data arguments last when practical It works better with threading macros and partial application
Keep the passed-in functions pure when possible The abstraction stays predictable and easier to test
Document the callback shape through examples Readers need to know what the function will receive and what it should return
Avoid wrapping core functions without adding meaning Indirection only helps when it captures a real pattern

Another Useful Pattern: Parameterize The Measurement

Many business calculations share the same traversal shape but differ in what they measure.

1(defn sum-by [metric-fn coll]
2  (reduce (fn [total item]
3            (+ total (metric-fn item)))
4          0M
5          coll))

You can now use the same structure with different metrics:

1(sum-by :order/amount orders)
2
3(sum-by (fn [{:order/keys [discount]}]
4          discount)
5        orders)

This is a good higher-order function because the reusable idea is clear: “sum a derived numeric value across the collection.”

Where Java Developers Usually Overdo It

When people are new to Clojure, they often recreate object-oriented extension patterns by building too many tiny wrappers.

Common overcorrections:

  • one custom function for every map
  • one callback abstraction per tiny branch of logic
  • callback names so generic that the call site stops teaching anything

That leads to the functional equivalent of Java over-engineering.

The better Clojure habit is:

  • keep direct uses of core functions when the intent is obvious
  • introduce a custom higher-order function only when it captures a pattern the domain actually repeats

A Simple Checklist

Before writing a custom function-taking function, ask:

Question If the answer is yes
Does it capture a real repeated pattern? It may deserve its own function
Does the callback name make the contract clear? The API will be easier to read
Would plain map, filter, or reduce already be clearer? Do not add the wrapper
Can the callback stay pure? Testing and reuse will be simpler

Choosing A Good Call Site

The call site should tell the story.

Good:

1(transform-matching overdue? escalate orders)
2(sum-by :invoice/amount invoices)

Weak:

1(apply-to-all transform orders)
2(do-stuff thing orders)

The strong version makes the domain intent visible. The weak version only hides a built-in behind a vague name.

Knowledge Check

### When does a custom higher-order function usually justify its existence? - [x] When it captures a real repeated domain pattern instead of merely renaming `map` or `filter` - [ ] Whenever any function can technically accept another function - [ ] Only when it mutates shared state - [ ] Only when it uses macros internally > **Explanation:** The abstraction should add meaning, not just indirection. A good custom higher-order function names a repeated pattern the domain actually cares about. ### Why is `transform-matching` a stronger abstraction than `apply-to-all`? - [x] It expresses a specific reusable behavior: transform only the items that match a predicate - [ ] It avoids all collection traversal internally - [ ] It prevents callers from passing predicates - [ ] It turns vectors into maps automatically > **Explanation:** `transform-matching` says something new and useful. `apply-to-all` mostly restates what `map` already means. ### What is one good API-design rule for custom function-taking functions? - [x] Make the callback contract obvious through names and examples - [ ] Hide the callback shape so the implementation can change freely - [ ] Prefer generic names like `thing` or `do-stuff` - [ ] Always place data arguments first regardless of readability > **Explanation:** The reader needs to understand what kind of function is expected and what role it plays. Clear names and examples make that contract visible. ### In `sum-by`, what does `metric-fn` represent? - [x] The function that derives the numeric value to aggregate from each item - [ ] The mutable state container used by `reduce` - [ ] A namespace alias for `clojure.core/+` - [ ] A comparator used to sort the collection before summing > **Explanation:** `sum-by` reuses the traversal and aggregation shape while letting the caller choose what number to extract from each item.
Revised on Friday, April 24, 2026