liveBook · Manning

Caching

As explained in Clojure: The Essential Reference

A cache is a storage structure that retains previously computed results for retrieval by associated keys or repeated access.

Caching retains a result so repeated access need not repeat the work. In Clojure, `memoize` wraps a function and stores each result in an internal map keyed by the arguments. A call with the same arguments returns the stored result without invoking the original function again. section 2.4.2

One computation per argument list

Define `f*` to print a cache-miss message and add two numbers, then set `f` to `(memoize f*)`. The first `(f 1 2)` prints the message and returns `3`; the second returns `3` without printing. `(f 1 3)` prints again and returns `4` because its arguments form a different key. section 2.4.2

Choose what to retain with the cost in mind:

  • Memoization suits repeated, non-trivial computations whose inputs and results have modest memory footprints. Caching string-pair Levenshtein distances avoids recalculating them while ranking spelling suggestions. section 2.4.2
  • Lazy sequences cache realized results instead of indexing function calls by argument lists. A `repeatedly` sequence generates items on demand; retaining its head can keep every requested item in memory, while releasing the head allows consumed items to be removed. section 9.2.1

The contracts differ. `memoize` requires a callable function and returns a variadic wrapper; omitting its function argument raises an `ArityException`. Sequence caching instead depends on the collection and when its elements are realized. When a sequence views a mutable Java list, an element realized before a list update can retain its earlier value, while one realized afterward can reflect the update. section 2.4.2 section 9.1.1

(def levenshtein (memoize2 levenshtein*))

(best "achive" dict-ac)
;; (["achieve" 1] ["achime" 1] ["active" 1])

(levenshtein :done)
;; {:calls 400, :hits 0, :misses 400 :count-chars 5168 :bytes 10376}

(best "achive" dict-ac)
;; (["achieve" 1] ["achime" 1] ["active" 1])

(levenshtein :done)
;; {:calls 800, :hits 400, :misses 400 :count-chars 5168 :bytes 10376}

A custom memoizing wrapper reports 400 misses on the first spelling-suggestion run, then 400 hits and no new misses on the second; its statistics also illustrate the retained keys' memory cost. section 2.4.2

How Clojure: The Essential Reference covers it

How deep it goes
Explains argument-keyed memoization through its contract and examples, then details how lazy realization affects sequence memory.
Examples
Shows repeated addition calls, cached spelling-distance rankings, and a lazy sequence viewing an updated Java list.
What it assumes
Assumes familiarity with Clojure functions and sequences while defining `memoize` through its input and output contract.
What you can do after
Apply it via `memoize` to reuse results, and evaluate when retained lazy-sequence elements increase memory use.
How it connects
Connects caching to memoization, Levenshtein distance, lazy sequences, and mutable collections.

Also explained in

Compare how 38 books explain caching →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Clojure: The Essential Reference. The section links point to the parts of the book it draws on.