Skip to content

Latest commit

 

History

History
98 lines (74 loc) · 3 KB

File metadata and controls

98 lines (74 loc) · 3 KB

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 Layers 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

import { Console, Effect, Layer, ServiceMap } from "effect"

const MyService = ServiceMap.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:

// 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.

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.

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.