99 lines
3.0 KiB
Markdown
99 lines
3.0 KiB
Markdown
# Layer Memoization
|
|
|
|
In v3, each call to `Effect.provide` created its own memoization scope. Layers
|
|
were memoized / deduplicated within a single `Effect.provide` call, but would
|
|
**not** be shared across separate calls — so two `Effect.provide` calls with
|
|
overlapping layers would silently build those layers twice.
|
|
|
|
In v4, the underlying `MemoMap` data structure which facilitates memoization of
|
|
`Layer`s is shared between `Effect.provide` calls (unless explicitly disabled
|
|
via the `{ local: true }` option). Thus, layers are automatically memoized /
|
|
deduplicated across `Effect.provide` calls.
|
|
|
|
## Example
|
|
|
|
```ts
|
|
import { Console, Context, Effect, Layer } from "effect"
|
|
|
|
const MyService = Context.Service<{ readonly value: string }>("MyService")
|
|
|
|
const MyServiceLayer = Layer.effect(
|
|
MyService,
|
|
Effect.gen(function*() {
|
|
yield* Console.log("Building MyService")
|
|
return { value: "hello" }
|
|
})
|
|
)
|
|
|
|
const program = Effect.gen(function*() {
|
|
const a = yield* MyService
|
|
return a.value
|
|
})
|
|
|
|
// Same layer provided twice in separate provide calls
|
|
const main = program.pipe(
|
|
Effect.provide(MyServiceLayer),
|
|
Effect.provide(MyServiceLayer)
|
|
)
|
|
|
|
// Effect v3: "Building MyService" is logged TWICE
|
|
// Effect v4: "Building MyService" is logged ONCE
|
|
Effect.runPromise(main)
|
|
```
|
|
|
|
## Prefer Layer Composition Over Multipl Provides
|
|
|
|
Even though v4 memoizes across `provide` calls, **composing layers before
|
|
providing is still the recommended pattern**. Layer composition makes your
|
|
dependency graph explicit and lets you see the full structure in one place:
|
|
|
|
```ts
|
|
// Preferred — provide once
|
|
const main = program.pipe(Effect.provide(MyServiceLayer))
|
|
```
|
|
|
|
The auto-memoization feature is a safety net to avoid the footguns associated
|
|
with multiple `Effect.provide` calls present in v3. It is **NOT** a substitute
|
|
for proper layer composition.
|
|
|
|
## Opting Out of Shared Memoization
|
|
|
|
There are cases where you **want** a layer to be built fresh — for example,
|
|
test isolation or creating independent resource pools. v4 provides two
|
|
mechanisms:
|
|
|
|
### `Layer.fresh`
|
|
|
|
Wraps a layer so it always builds with a fresh memo map, bypassing the shared
|
|
cache. This existed in v3 as well.
|
|
|
|
```ts
|
|
import { Effect, Layer } from "effect"
|
|
|
|
const main = program.pipe(
|
|
Effect.provide(MyServiceLayer),
|
|
Effect.provide(Layer.fresh(MyServiceLayer))
|
|
)
|
|
// "Building MyService" is logged TWICE — fresh bypasses the shared cache
|
|
```
|
|
|
|
### `Effect.provide` with `{ local: true }`
|
|
|
|
New in v4. Builds the provided layer with a **local memo map** instead of the
|
|
fiber's shared one. The layer and all its sublayers are built from scratch and
|
|
are not shared with other `provide` calls.
|
|
|
|
```ts
|
|
import { Effect } from "effect"
|
|
|
|
const main = program.pipe(
|
|
Effect.provide(MyServiceLayer),
|
|
Effect.provide(MyServiceLayer, { local: true })
|
|
)
|
|
// "Building MyService" is logged TWICE — local creates its own memo map
|
|
```
|
|
|
|
Use `local: true` when you need an entire layer subtree to be isolated — for
|
|
example, when providing layers in a test harness where each test should get
|
|
independent resources.
|