2 Getting Started
Last modified: 2026-02-08
Pocket is a Clojure library for filesystem-based caching of expensive computations. It persists results to disk so they survive JVM restarts. Cache keys are derived from the function identity and its arguments, so the same computation always maps to the same cache entry.
Setup
(ns pocket-book.getting-started
(:require
;; Pocket API:
[scicloj.pocket :as pocket]
;; Annotating kinds of visualizations:
[scicloj.kindly.v4.kind :as kind]
;; Logging setup for this chapter (see Logging chapter):
[pocket-book.logging]))First, we set up a cache directory and define an expensive computation:
(def cache-dir "/tmp/pocket-demo")(pocket/set-base-cache-dir! cache-dir)[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] INFO scicloj.pocket - Cache dir set to: /tmp/pocket-demo
"/tmp/pocket-demo"Start fresh so the examples below run from a clean slate:
(pocket/cleanup!)[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] INFO scicloj.pocket - Cache cleanup: /tmp/pocket-demo
{:dir "/tmp/pocket-demo", :existed false}(defn expensive-calculation
"Simulates an expensive computation"
[x y]
(println (str "Computing " x " + " y " (this is expensive!)"))
(Thread/sleep 400)
(+ x y))Background: deref in Clojure
In Clojure, deref extracts a value from a reference type. It can be written as (deref x) or with the shorthand reader macro @x — both are equivalent. Pocket’s cached returns a Cached object that implements IDeref, so you use @ (or deref) to trigger the computation and retrieve the result.
Creating a cached computation
cached creates a lazy cached computation. It returns a Cached object — the computation won’t run until we deref it:
(def cached-result
(pocket/cached #'expensive-calculation 10 20))(type cached-result)scicloj.pocket.impl.cache.CachedFirst deref (computes and caches):
(time @cached-result)[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] INFO scicloj.pocket.impl.cache - Cache miss, computing: pocket-book.getting-started/expensive-calculation
Computing 10 + 20 (this is expensive!)
[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] DEBUG scicloj.pocket.impl.cache - Cache write: /tmp/pocket-demo/fb/(pocket-book.getting-started_expensive-calculation 10 20)
"Elapsed time: 408.302946 msecs"
30Second deref (loaded from cache, instant):
(time @cached-result)"Elapsed time: 0.221899 msecs"
30Wrapping functions with caching-fn
For convenience, caching-fn wraps a function so that every call returns a Cached object:
(def cached-expensive
(pocket/caching-fn #'expensive-calculation))First call:
(time @(cached-expensive 5 15))[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] INFO scicloj.pocket.impl.cache - Cache miss, computing: pocket-book.getting-started/expensive-calculation
Computing 5 + 15 (this is expensive!)
[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] DEBUG scicloj.pocket.impl.cache - Cache write: /tmp/pocket-demo/c4/(pocket-book.getting-started_expensive-calculation 5 15)
"Elapsed time: 412.912495 msecs"
20Same args — cache hit:
(time @(cached-expensive 5 15))"Elapsed time: 0.395777 msecs"
20Different args — new computation:
(time @(cached-expensive 7 8))[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] INFO scicloj.pocket.impl.cache - Cache miss, computing: pocket-book.getting-started/expensive-calculation
Computing 7 + 8 (this is expensive!)
[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] DEBUG scicloj.pocket.impl.cache - Cache write: /tmp/pocket-demo/a2/(pocket-book.getting-started_expensive-calculation 7 8)
"Elapsed time: 401.763924 msecs"
15Nil handling
Pocket properly handles nil values. Since the cache uses files on disk, it needs to distinguish “never computed” from “computed and got nil”. It does this with a special marker file:
(defn returns-nil [] nil)(def nil-result (pocket/cached #'returns-nil))Cached nil value:
(deref nil-result)[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] INFO scicloj.pocket.impl.cache - Cache miss, computing: pocket-book.getting-started/returns-nil
[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] DEBUG scicloj.pocket.impl.cache - Cache write: /tmp/pocket-demo/7a/(pocket-book.getting-started_returns-nil)
nilLoading nil from cache:
(deref nil-result)nilImportant: use vars or keywords for functions
Always pass functions as vars (#'fn-name) or keywords, not as bare function objects. Vars have stable names that produce consistent cache keys across sessions. Keywords are useful for extracting from cached maps (e.g., (cached :train split-c)). Pocket throws an error if you pass a bare function:
;; ✅ (pocket/cached #'my-function args)
;; ✅ (pocket/cached :train cached-map)
;; ❌ (pocket/cached my-function args)See the Usage Practices chapter for a detailed explanation and more best practices.
Next steps
Configuration — cache directory, in-memory eviction policies,
pocket.ednRecursive Caching in Pipelines — chaining cached computations
Usage Practices — invalidation strategies, testing, serialization, and more
Cleanup
(pocket/cleanup!)[nREPL-session-120ee500-d4ba-4b41-bcdc-e26822f35e2b] INFO scicloj.pocket - Cache cleanup: /tmp/pocket-demo
{:dir "/tmp/pocket-demo", :existed true}