Context.make

Context.empty (22) , Context.add (20) and Context.get (21) are enough to do all the main operations on a context. There are many other helper functions. Most of them rely on these three builders.

Context.make creates a context with one item. That item might be using a Context.GenericTag (19)

import { Context } from "effect";
const MinimumScore = Context.GenericTag<"MinimumScore", number>(
"@fred.c/MinimumScore",
);
const scoreContext = Context.make(MinimumScore, 3);
// ...later
const three = Context.get(scoreContext, MinimumScore);

Or it might be using a Context.Tag (16)

class DiceService extends Context.Tag("@fred.c/DiceService")<
DiceService,
{ roll: () => number }
>() {}
const diceContext = Context.make(DiceService, {
roll: () => Math.ceil(Math.random() * 6),
});
// ...later
const diceRoll = Context.get(diceContext, DiceService).roll();

Note that we could implement this as

const make = <T extends Context.Tag<any, any>>(
tag: T,
service: Context.Tag.Service<T>,
): Context.Context<Context.Tag.Identifier<T>> =>
Context.empty().pipe(Context.add(tag, service));
// usage:
const scoreContext = make(MinimumScore, 2);

Behind the scenes this is implemented by constructing a Map directly.

Here is the implementation:

const ContextProto: Omit<C.Context<unknown>, "unsafeMap"> = {
// defines a few methods, e.g. `pipe` and `toString`,
// that should be available on every Context
// elided here for brevity
}
const makeContext = <Services>(unsafeMap: Map<string, any>): C.Context<Services> => {
const context = Object.create(ContextProto)
context.unsafeMap = unsafeMap
return context
}
const make = <T extends C.Tag<any, any>>(
tag: T,
service: C.Tag.Service<T>
): C.Context<C.Tag.Identifier<T>> => makeContext(new Map([[tag.key, service]]))

The Effect source code can be intimidating at first. Most functions are quite short and simple, but it can be hard to unpick how the pieces fit together.

Making a Context with one service is so common that it is exposed directly as a method on tags. Effect does not expose many instance methods, but in this case it is convenient.

const sixContext = MinimumScore.context(6)
const lowRollContext = DiceService.context({
roll: () => 1
})

Behind the scenes this called Context.make.

Let’s recap

  • A Context maps tags to service implementations
  • Operations on Context are plain (non-effectful) operations
  • There are multiple equivalent ways to make a Context with one service
  • Use whichever is most convenient for your current situation
  • Effect optimises some operations behind the scenes, using internal implementation details