Caching
Most real programs repeat work: the same user is fetched twice, the same configuration is parsed on every request, the same database connection is opened by a dozen fibers at once. Effect’s caching modules let you compute a value once and reuse it — safely under concurrency, with control over lifetime, capacity, and resource cleanup.
There are two families of tools here, and they solve different problems:
- Memoizing values — store the result of a lookup effect keyed by input.
Concurrent misses for the same key share a single in-flight computation, and
both successes and failures are cached. This is
Cache(andScopedCachewhen the cached values own resources). - Sharing resources — store a live resource (a connection, client, or
session) and track how many fibers are using it, releasing it automatically
when the last borrower lets go. This is
RcMapandRcRef.
A third tool, Resource, sits in between: it keeps a
single value loaded in memory and lets you refresh it on demand or on a
schedule.
Which tool do I reach for?
Section titled “Which tool do I reach for?”| You want to… | Use |
|---|---|
| Cache lookups by key, deduplicate concurrent misses, cap size with a TTL | Cache |
| Cache lookups whose values acquire scoped resources | ScopedCache |
| Memoize a single effect (no key) | Effect.cached — see Cache |
| Share one scoped resource across many fibers, ref-counted | RcRef |
| Share scoped resources by key, ref-counted | RcMap |
| Keep a value loaded and refresh it manually or on a schedule | Resource |
A first taste
Section titled “A first taste”The simplest cache is keyed by an input and backed by a lookup effect. Concurrent requests for the same key only run the lookup once.
import { Cache, Effect } from "effect"
const program = Effect.gen(function*() { // A cache mapping user ids to (here, trivially computed) names. // The lookup runs at most once per key until the entry is evicted. const cache = yield* Cache.make({ capacity: 1000, lookup: (id: number) => Effect.succeed(`user-${id}`) })
const a = yield* Cache.get(cache, 1) // miss: runs the lookup const b = yield* Cache.get(cache, 1) // hit: returns the cached value return [a, b]})In this section
Section titled “In this section”- Cache and ScopedCache — memoize lookups by key, with
capacity limits, fixed or dynamic TTLs, and scoped-resource variants. Also
covers the no-key
Effect.cachedhelpers. - Reference counting —
RcMapandRcRefshare scoped resources and release them when the last reference goes away. - Resource — keep a single value loaded and refresh it
manually or automatically with a
Schedule.
Caching builds on a few concepts covered elsewhere: lookup functions are
ordinary Effects, TTLs use Duration, scoped
resources rely on scopes, and automatic refresh uses
Schedule.