We have seen that there are multiple ways to construct a Layer. Layer.succeed (27)
allows us to construct a Layer from a value. Layer.effect (28)
allows us to construct a Layer from an Effect. Layer.succeedContext (29)
allows us to construct a Layer from a Context.
Layer.effectContext (30)
allows us to combine the power of Layer.effect (28)
and Layer.succeedContext (29)
.
Let us consider the same Effectful program we have seen previously.
export const business = Effect.gen(function* () { const dice = yield* DiceService; const coin = yield* CoinService; let total = yield* dice.roll; const flip = yield* coin.flip; if (flip === "HEADS") { total += yield* dice.roll; } if (total < 5) { yield* new YouLose(); } return total;});Since our app requires a DiceService and a CoinService, we would like to provide those as a Layer. The real implementation will require using RandomService behind the scenes, but we would prefer to keep our services decoupled. Here is an implementation of ServicesLive that reflects exactly what we want.
type MakeServices = Effect.Effect< Context.Context<DiceService | CoinService>, never, RandomService>;
const makeServices: MakeServices = Effect.gen(function* () { const random = yield* RandomService; return Context.empty().pipe( Context.add(DiceService, { roll: random.next.pipe(Effect.map((v) => Math.ceil(v * 6))), }), Context.add(CoinService, { flip: random.next.pipe( Effect.map((v): Coin => (v > 0.5 ? "HEADS" : "TAILS")) ), }) );});
// ┌─── Layer.Layer<DiceService | CoinService, never, RandomService>// ▼const ServicesLive = Layer.effectContext(makeServices);In order to run our app we need to provide an implementation of RandomService too.
const RandomLive = Layer.succeed(RandomService, { next: Effect.sync(() => Math.random()),});
business.pipe( Effect.provide(ServicesLive), Effect.provide(RandomLive), Effect.runSync);We could test our DiceService on its own, or integrated with the rest of the app.
const RandomTest = Layer.succeed(RandomService, { next: Effect.succeed(0.999),});
const testResult = business.pipe( Effect.provide(ServicesLive), Effect.provide(RandomTest), Effect.runSync);assert.equal(testResult, 12); Layer.effectContext (30)
is the most powerful constructor. We can implement the other constructors using it.
import { Context, Effect, Layer, Types } from "effect";
const succeed = <I, S>( tag: Context.Tag<I, S>, resource: Types.NoInfer<S>): Layer.Layer<I> => { const context = Context.make(tag, resource); const effect = Effect.succeed(context); return Layer.effectContext(effect);};
const succeedContext = <A>(context: Context.Context<A>): Layer.Layer<A> => { const effect = Effect.succeed(context); return Layer.effectContext(effect);};
const effect = <I, S, E, R>( tag: Context.Tag<I, S>, effect: Effect.Effect<Types.NoInfer<S>, E, R>): Layer.Layer<I, E, R> => { const contextEffect = Effect.map(effect, (service) => Context.make(tag, service) ); return Layer.effectContext(contextEffect);};Recall from Effect.provide (26)
that we wanted a function along the lines of provideContextEffect. We can now see how Layers offer us this functionality packaged in a convenient and flexible data structure.
// what we wantedtype ProvideContextEffectfully = <ROut, E1, RIn>( context: Effect.Effect<Context.Context<ROut>, E1, RIn>) => <A, E2, R>( effect: Effect.Effect<A, E2, R>) => Effect.Effect<A, E1 | E2, RIn | Exclude<R, ROut>>;
export const provideContextEffect: ProvideContextEffectfully = (context) => (effect) => Effect.gen(function* () { const built = yield* context; const ran = yield* effect.pipe(Effect.provide(built)); return ran; });
// what we now haveexport const provideLayer: ProvideContextEffectfully = (context) => { const layer = Layer.effectContext(context); return Effect.provide(layer);};In the next few lessons we will see how to combine Layers, much in the same way we can combine Effects or Contexts.
Let’s recap:
Layercombines aspects ofEffectandContext- a Layer represents instructions to make a concrete instance of a service or services
-
Layer.effectContext(30) is a powerful low-level constructor for Layers - Other Layer constructors are often more convenient