Layer.map
Recall that Effect.map
(4)
can transform the success channel of an Effect by applying a pure (non-Effectful) function. Similarly, Layer.map
can apply a pure function to transform the output channel of a Layer.
Here is an example using the services from Layer.effectContext
(29)
. The code is functionally equivalent, but arguably more readable.
import {
RandomService,
DiceService,
CoinService,
type Coin,
business,
} from "./lib";
const nextRandom = RandomService.pipe(
Effect.flatMap((service) => service.next)
);
const roll = nextRandom.pipe(Effect.map((v) => Math.ceil(v * 6)));
const flip = nextRandom.pipe(
Effect.map((v): Coin => (v > 0.5 ? "HEADS" : "TAILS"))
);
// ┌─── (self: Layer.Layer<RandomService>) => Layer.Layer<Services>
// ▼
const upgrade = Layer.map<RandomService, DiceService | CoinService>((context) =>
Context.empty().pipe(
Context.add(DiceService, { roll: Effect.provide(roll, context) }),
Context.add(CoinService, { flip: Effect.provide(flip, context) })
)
);
// ┌─── Layer.Layer<RandomService>
// ▼
const RandomLive = Layer.succeed(RandomService, {
next: Effect.sync(Math.random),
});
// ┌─── Layer.Layer<DiceService | CoinService>
// ▼
const ServicesLive = upgrade(RandomLive);
business.pipe(Effect.provide(ServicesLive), Effect.runPromise);
Layer.map
behaves much the same way as Effect.map
(4)
, but a few differences. When we map over an Effect, we have a single value, and the mapping function applies to that. When we map over a Layer, we are mapping over the Context inside of it. To understand why, consider replacing A = RandomService
and B = DiceService | CoinService
in the type signatures below.
type L<A> = Layer.Layer<A, Err, Req>; // for some Err, Req
type C<A> = Context.Context<A>;
// "normal" map, as defined on a functor
type mapExpected = <A, B>(la: L<A>, fab: (a: A) => B) => L<B>;
// "actual" map, as defined in Layer.map
type mapActual = <A, B>(la: L<A>, fcacb: (a: C<A>) => C<B>) => L<B>;
As you might expect, Layer.flatMap
and Layer.mapError
also exist. Layer.map
and Layer.flatMap
are not used that often in application code. The main purpose of this lesson was to show that we can manipulate and transform Layers. Layer.map
allows us to take some kind of Layer as an argument and return a different kind of Layer.
In the next few lessons, we’ll go one step further using Layer combinators. Recall from Effect.flatMap
(6)
that a combinator is a function that takes multiple arguments of type T
and returns a new structure containing one “combined” T
.
Let’s recap:
Layer
combines aspects ofEffect
andContext
- Much like other generic container types (Effect, Context, Array, Promise) it is possible to transform and combine Layers
Layer.map
can transform the output channel of a Layer- Other Layer transformations are often more convenient