Context.make
Context.empty
, Context.add
and Context.get
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
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
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