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 of Effect and Context
  • 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