diff --git a/.changeset/little-tools-cough.md b/.changeset/little-tools-cough.md new file mode 100644 index 00000000000..fbf0544154a --- /dev/null +++ b/.changeset/little-tools-cough.md @@ -0,0 +1,5 @@ +--- +"effect": minor +--- + +remove Env, EnvRef & FiberFlags from Micro diff --git a/packages/effect/src/Micro.ts b/packages/effect/src/Micro.ts index 50813ae715d..e8e886e56e3 100644 --- a/packages/effect/src/Micro.ts +++ b/packages/effect/src/Micro.ts @@ -18,6 +18,7 @@ import * as Hash from "./Hash.js" import type { TypeLambda } from "./HKT.js" import type { Inspectable } from "./Inspectable.js" import { format, NodeInspectSymbol, toStringUnknown } from "./Inspectable.js" +import * as InternalContext from "./internal/context.js" import * as doNotation from "./internal/doNotation.js" import { StructuralPrototype } from "./internal/effectable.js" import * as Option from "./Option.js" @@ -422,8 +423,8 @@ export interface Fiber { readonly [FiberTypeId]: Fiber.Variance readonly currentOpCount: number - readonly getEnvRef: (ref: EnvRef) => A - readonly env: Env + readonly getRef: (ref: Context.Reference) => A + readonly context: Context.Context readonly addObserver: (cb: (exit: MicroExit) => void) => () => void readonly unsafeInterrupt: () => void readonly unsafePoll: () => MicroExit | undefined @@ -446,19 +447,6 @@ export declare namespace Fiber { } } -/** - * @since 3.11.0 - * @experimental - * @category Fiber - */ -export const FiberFlag = { - Uninterruptible: 1 -} as const - -const isInterruptible = (flags: number): boolean => (flags & 1) === 0 - -const defaultFlags = 0 - const fiberVariance = { _A: identity, _E: identity @@ -475,14 +463,14 @@ class FiberImpl implements Fiber { public currentOpCount = 0 constructor( - public env: Env, - public flags = defaultFlags + public context: Context.Context, + public interruptible = true ) { this[FiberTypeId] = fiberVariance } - getEnvRef(ref: EnvRef): A { - return this.env.refs[ref.key] as A ?? ref.initial + getRef(ref: Context.Reference): A { + return InternalContext.unsafeGetReference(this.context, ref) } addObserver(cb: (exit: MicroExit) => void): () => void { @@ -505,7 +493,7 @@ class FiberImpl implements Fiber { return } this._interrupted = true - if (isInterruptible(this.flags)) { + if (this.interruptible) { this.evaluate(exitInterrupt as any) } } @@ -548,7 +536,7 @@ class FiberImpl implements Fiber { try { while (true) { this.currentOpCount++ - if (!yielding && this.getEnvRef(currentScheduler).shouldYield(this as any)) { + if (!yielding && this.getRef(CurrentScheduler).shouldYield(this as any)) { yielding = true const prev = current current = flatMap(yieldNow, () => prev as any) as any @@ -842,7 +830,7 @@ export const failCause: (cause: MicroCause) => Micro = makeExit( prop: "cause", eval(fiber) { let cont = fiber.getCont(failureCont) - while (causeIsInterrupt(this[args]) && cont && isInterruptible(fiber.flags)) { + while (causeIsInterrupt(this[args]) && cont && fiber.interruptible) { cont = fiber.getCont(failureCont) } return cont ? cont[failureCont](this[args], fiber) : fiber.yieldWith(this) @@ -906,7 +894,7 @@ export const yieldNowWith: (priority?: number) => Micro = makePrimitive({ op: "Yield", eval(fiber) { let resumed = false - fiber.getEnvRef(currentScheduler).scheduleTask(() => { + fiber.getRef(CurrentScheduler).scheduleTask(() => { if (resumed) return fiber.evaluate(exitVoid as any) }, this[args] ?? 0) @@ -1114,7 +1102,7 @@ export const withFiber: ( * @category constructors */ export const yieldFlush: Micro = withFiber((fiber) => { - fiber.getEnvRef(currentScheduler).flush() + fiber.getRef(CurrentScheduler).flush() return exitVoid }) @@ -1160,9 +1148,9 @@ const asyncOptions: ( const asyncFinalizer: (onInterrupt: () => Micro) => Primitive = makePrimitive({ op: "AsyncFinalizer", ensure(fiber) { - if (isInterruptible(fiber.flags)) { - fiber.flags |= FiberFlag.Uninterruptible - fiber._stack.push(revertFlags(FiberFlag.Uninterruptible)) + if (fiber.interruptible) { + fiber.interruptible = false + fiber._stack.push(setInterruptible(true)) } }, contE(cause, _fiber) { @@ -1570,47 +1558,6 @@ export const map: { (self: Micro, f: (a: A) => B): Micro => flatMap(self, (a) => succeed(f(a))) ) -/** - * Update the fiber flags for the duration of the effect. - * - * @since 3.4.0 - * @experimental - * @category flags - */ -export const updateFiberFlags = (f: (flags: number) => number): Micro => updateFlags(f) - -const updateFlags: ( - f: (flags: number) => number, - evaluate?: (oldFlags: number) => Micro -) => any = makePrimitive({ - op: "UpdateFlags", - single: false, - eval(fiber) { - const [f, evaluate] = this[args] - const oldFlags = fiber.flags - const newFlags = f(fiber.flags) - if (fiber._interrupted && isInterruptible(newFlags)) { - return exitInterrupt - } else if (newFlags === oldFlags) { - return evaluate ? evaluate(oldFlags) : exitVoid - } - const diff = oldFlags ^ newFlags - fiber.flags = newFlags - fiber._stack.push(revertFlags(diff)) - return evaluate ? evaluate(oldFlags) : exitVoid - } -}) - -const revertFlags: (diff: number) => Primitive = makePrimitive({ - op: "RevertFlags", - ensure(fiber) { - fiber.flags = fiber.flags ^ this[args] - if (fiber._interrupted && isInterruptible(fiber.flags)) { - return () => exitInterrupt - } - } -}) - // ---------------------------------------------------------------------------- // MicroExit // ---------------------------------------------------------------------------- @@ -1843,7 +1790,7 @@ export class MicroSchedulerDefault implements MicroScheduler { * @since 3.5.9 */ shouldYield(fiber: Fiber) { - return fiber.currentOpCount >= fiber.getEnvRef(currentMaxOpsBeforeYield) + return fiber.currentOpCount >= fiber.getRef(MaxOpsBeforeYield) } /** @@ -1856,106 +1803,6 @@ export class MicroSchedulerDefault implements MicroScheduler { } } -// ---------------------------------------------------------------------------- -// env -// ---------------------------------------------------------------------------- - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export const EnvTypeId = Symbol.for("effect/Micro/Env") - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export type EnvTypeId = typeof EnvTypeId - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export interface Env extends Pipeable { - readonly [EnvTypeId]: { - _R: Covariant - } - readonly refs: Record -} - -const EnvProto = { - [EnvTypeId]: { - _R: identity - }, - pipe() { - return pipeArguments(this, arguments) - } -} - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export const envMake = (refs: Record): Env => { - const self = Object.create(EnvProto) - self.refs = refs - return self -} - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export const envUnsafeMakeEmpty = (): Env => envMake(Object.create(null)) - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export const envGet: { - (ref: EnvRef): (self: Env) => A - (self: Env, ref: EnvRef): A -} = dual( - 2, - (self: Env, ref: EnvRef): A => ref.key in self.refs ? (self.refs[ref.key] as A) : ref.initial -) - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export const envSet: { - (ref: EnvRef, value: A): (self: Env) => Env - (self: Env, ref: EnvRef, value: A): Env -} = dual(3, (self: Env, ref: EnvRef, value: A): Env => { - const refs = Object.assign(Object.create(null), self.refs) - refs[ref.key] = value - return envMake(refs) -}) - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export const envMutate: { - (f: (map: Record) => void): (self: Env) => Env - (self: Env, f: (map: Record) => void): Env -} = dual( - 2, - ( - self: Env, - f: (map: Record) => Record - ): Env => envMake(f(Object.assign(Object.create(null), self.refs))) -) - /** * Access the given `Context.Tag` from the environment. * @@ -1963,8 +1810,12 @@ export const envMutate: { * @experimental * @category environment */ -export const service = (tag: Context.Tag): Micro => - withFiber((fiber) => succeed(Context.unsafeGet(fiber.getEnvRef(currentContext), tag))) +export const service: { + (tag: Context.Reference): Micro + (tag: Context.Tag): Micro +} = + ((tag: Context.Tag): Micro => + withFiber((fiber) => succeed(Context.unsafeGet(fiber.context, tag)))) as any /** * Access the given `Context.Tag` from the environment, without tracking the @@ -1979,73 +1830,81 @@ export const service = (tag: Context.Tag): Micro => */ export const serviceOption = ( tag: Context.Tag -): Micro> => withFiber((fiber) => succeed(Context.getOption(fiber.getEnvRef(currentContext), tag))) - -/** - * Retrieve the current value of the given `EnvRef`. - * - * @since 3.4.0 - * @experimental - * @category environment - */ -export const getEnvRef = (envRef: EnvRef): Micro => withFiber((fiber) => succeed(fiber.getEnvRef(envRef))) +): Micro> => withFiber((fiber) => succeed(Context.getOption(fiber.context, tag))) /** - * Set the value of the given `EnvRef` for the duration of the effect. + * Update the Context with the given mapping function. * - * @since 3.4.0 + * @since 3.11.0 * @experimental * @category environment */ -export const locally: { - ( - fiberRef: EnvRef, - value: A - ): (self: Micro) => Micro - ( - self: Micro, - fiberRef: EnvRef, - value: A - ): Micro +export const updateContext: { + ( + f: (context: Context.Context) => Context.Context> + ): (self: Micro) => Micro + (self: Micro, f: (context: Context.Context) => Context.Context>): Micro } = dual( - 3, - ( - self: Micro, - fiberRef: EnvRef, - value: A - ): Micro => locallyWith(self, fiberRef, () => value) + 2, + ( + self: Micro, + f: (context: Context.Context) => Context.Context> + ): Micro => + withFiber((fiber) => { + const prev = fiber.context as Context.Context + fiber.context = f(prev) + return onExit( + self as any, + () => { + fiber.context = prev + return void_ + } + ) + }) ) /** - * Update the value of the given `EnvRef` for the duration of the effect. + * Update the service for the given `Context.Tag` in the environment. * - * @since 3.4.0 + * @since 3.11.0 * @experimental * @category environment */ -export const locallyWith: { - ( - fiberRef: EnvRef, +export const updateService: { + ( + tag: Context.Reference, f: (value: A) => A ): (self: Micro) => Micro - ( + ( + tag: Context.Tag, + f: (value: A) => A + ): (self: Micro) => Micro + ( self: Micro, - fiberRef: EnvRef, + tag: Context.Reference, f: (value: A) => A ): Micro + ( + self: Micro, + tag: Context.Tag, + f: (value: A) => A + ): Micro } = dual( 3, - ( + ( self: Micro, - fiberRef: EnvRef, + tag: Context.Reference, f: (value: A) => A ): Micro => withFiber((fiber) => { - const prev = fiber.getEnvRef(fiberRef) - fiber.env = envSet(fiber.env, fiberRef, f(prev)) - return ensuring( + const prev = Context.unsafeGet(fiber.context, tag) + fiber.context = Context.add(fiber.context, tag, f(prev)) + return onExit( self, - sync(() => fiber.env = envSet(fiber.env, fiberRef, prev)) + () => { + fiber.context = Context.add(fiber.context, tag, prev) + return void_ + } ) }) ) @@ -2057,7 +1916,8 @@ export const locallyWith: { * @experimental * @category environment */ -export const context = (): Micro> => getEnvRef(currentContext) as any +export const context = (): Micro> => getContext as any +const getContext = withFiber((fiber) => succeed(fiber.context)) /** * Merge the given `Context` with the current context. @@ -2079,7 +1939,7 @@ export const provideContext: { ( self: Micro, provided: Context.Context - ): Micro> => locallyWith(self, currentContext, Context.merge(provided)) as any + ): Micro> => updateContext(self, Context.merge(provided)) as any ) /** @@ -2105,7 +1965,7 @@ export const provideService: { self: Micro, tag: Context.Tag, service: S - ): Micro> => locallyWith(self, currentContext, Context.add(tag, service)) as any + ): Micro> => updateContext(self, Context.add(tag, service)) as any ) /** @@ -2136,90 +1996,47 @@ export const provideServiceEffect: { ) // ======================================================================== -// Env refs +// References // ======================================================================== -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export const EnvRefTypeId: unique symbol = Symbol.for("effect/Micro/EnvRef") - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export type EnvRefTypeId = typeof EnvRefTypeId - -/** - * @since 3.4.0 - * @experimental - * @category environment - */ -export interface EnvRef { - readonly [EnvRefTypeId]: EnvRefTypeId - readonly key: string - readonly initial: A -} - -const EnvRefProto: ThisType> = { - [EnvRefTypeId]: EnvRefTypeId -} - -/** - * @since 3.4.0 - * @experimental - * @category environment refs - */ -export const envRefMake = (key: string, initial: LazyArg): EnvRef => - globalValue(key, () => { - const self = Object.create(EnvRefProto) - self.key = key - self.initial = initial() - return self - }) - /** * @since 3.11.0 * @experimental - * @category environment refs + * @category references */ -export const currentMaxOpsBeforeYield: EnvRef = envRefMake( +export class MaxOpsBeforeYield extends Context.Reference()< "effect/Micro/currentMaxOpsBeforeYield", - () => 2048 -) - -/** - * @since 3.4.0 - * @experimental - * @category environment refs - */ -export const currentContext: EnvRef> = envRefMake( - "effect/Micro/currentContext", - () => Context.empty() -) + number +>( + "effect/Micro/currentMaxOpsBeforeYield", + { defaultValue: () => 2048 } +) {} /** - * @since 3.4.0 + * @since 3.11.0 * @experimental * @category environment refs */ -export const currentConcurrency: EnvRef<"unbounded" | number> = envRefMake( +export class CurrentConcurrency extends Context.Reference()< "effect/Micro/currentConcurrency", - () => "unbounded" -) + "unbounded" | number +>( + "effect/Micro/currentConcurrency", + { defaultValue: () => "unbounded" } +) {} /** - * @since 3.4.0 + * @since 3.11.0 * @experimental * @category environment refs */ -export const currentScheduler: EnvRef = envRefMake( +export class CurrentScheduler extends Context.Reference()< "effect/Micro/currentScheduler", - () => new MicroSchedulerDefault() -) + MicroScheduler +>( + "effect/Micro/currentScheduler", + { defaultValue: () => new MicroSchedulerDefault() } +) {} /** * If you have a `Micro` that uses `concurrency: "inherit"`, you can use this @@ -2250,7 +2067,7 @@ export const withConcurrency: { ( self: Micro, concurrency: "unbounded" | number - ): Micro => locally(self, currentConcurrency, concurrency) + ): Micro => provideService(self, CurrentConcurrency, concurrency) ) // ---------------------------------------------------------------------------- @@ -3794,9 +3611,23 @@ export const interrupt: Micro = failCause(causeInterrupt()) */ export const uninterruptible = ( self: Micro -): Micro => updateFlags(addUninterruptible, () => self) +): Micro => + withFiber((fiber) => { + if (!fiber.interruptible) return self + fiber.interruptible = false + fiber._stack.push(setInterruptible(true)) + return self + }) -const addUninterruptible = (flags: number) => flags | FiberFlag.Uninterruptible +const setInterruptible: (interruptible: boolean) => Primitive = makePrimitive({ + op: "SetInterruptible", + ensure(fiber) { + fiber.interruptible = this[args] + if (fiber._interrupted && fiber.interruptible) { + return () => exitInterrupt + } + } +}) /** * Flag the effect as interruptible, which means that when the effect is @@ -3808,9 +3639,14 @@ const addUninterruptible = (flags: number) => flags | FiberFlag.Uninterruptible */ export const interruptible = ( self: Micro -): Micro => updateFlags(removeUninterruptible, () => self) - -const removeUninterruptible = (flags: number) => flags & ~FiberFlag.Uninterruptible +): Micro => + withFiber((fiber) => { + if (fiber.interruptible) return self + fiber.interruptible = true + fiber._stack.push(setInterruptible(false)) + if (fiber._interrupted) return exitInterrupt + return self + }) /** * Wrap the given `Micro` effect in an uninterruptible region, preventing the @@ -3836,7 +3672,12 @@ export const uninterruptibleMask = ( restore: (effect: Micro) => Micro ) => Micro ): Micro => - updateFlags(addUninterruptible, (oldFlags) => (oldFlags & 1) === 0 ? f(interruptible) : f(identity)) + withFiber((fiber) => { + if (!fiber.interruptible) return f(identity) + fiber.interruptible = false + fiber._stack.push(setInterruptible(true)) + return f(interruptible) + }) // ======================================================================== // collecting & elements @@ -4016,7 +3857,7 @@ export const forEach: { }): Micro => withFiber((parent) => { const concurrencyOption = options?.concurrency === "inherit" - ? parent.getEnvRef(currentConcurrency) + ? parent.getRef(CurrentConcurrency) : options?.concurrency ?? 1 const concurrency = concurrencyOption === "unbounded" ? Number.POSITIVE_INFINITY @@ -4245,7 +4086,7 @@ const unsafeFork = ( immediate = false, daemon = false ): Fiber => { - const child = new FiberImpl(parent.env, parent.flags) + const child = new FiberImpl(parent.context, parent.interruptible) if (!daemon) { parent.children().add(child) child.addObserver(() => parent.children().delete(child)) @@ -4253,7 +4094,7 @@ const unsafeFork = ( if (immediate) { child.evaluate(effect as any) } else { - parent.getEnvRef(currentScheduler).scheduleTask(() => child.evaluate(effect as any), 0) + parent.getRef(CurrentScheduler).scheduleTask(() => child.evaluate(effect as any), 0) } return child } @@ -4343,9 +4184,9 @@ export const runFork = ( readonly scheduler?: MicroScheduler | undefined } | undefined ): FiberImpl => { - const env = envUnsafeMakeEmpty() - env.refs[currentScheduler.key] = options?.scheduler ?? new MicroSchedulerDefault() - const fiber = new FiberImpl(env) + const fiber = new FiberImpl(CurrentScheduler.context( + options?.scheduler ?? new MicroSchedulerDefault() + )) fiber.evaluate(effect as any) if (options?.signal) { if (options.signal.aborted) { diff --git a/packages/effect/test/Micro.test.ts b/packages/effect/test/Micro.test.ts index b30c26b189c..1c61d1b9f94 100644 --- a/packages/effect/test/Micro.test.ts +++ b/packages/effect/test/Micro.test.ts @@ -389,8 +389,7 @@ describe.concurrent("Micro", () => { it.effect("context", () => Effect.gen(function*() { - const result = yield* ATag.pipe( - Micro.service, + const result = yield* Micro.service(ATag).pipe( Micro.map((_) => _) ) assert.deepStrictEqual(result, "A")