Skip to content

Latest commit

 

History

History
204 lines (197 loc) · 5.53 KB

16. Resource Handling: Advanced Scopes.md

File metadata and controls

204 lines (197 loc) · 5.53 KB

16. Resource Handling: Advanced Scopes

16.1 Scopes Revisited

// Scope.ts
interface Scope {}
interface CloseableScope extends Scope {}

declare const addFinalizer: (
  self: Scope,
  finalizer: Effect<unknown>
) => Effect<void>
declare const addFinalizerExit: (
    self: Scope, 
    finalizer: Finalizer
) => Effect<void>
declare const close: (
    self: CloseableScope, 
    exit: Exit<unknown, unknown>
) => Effect<void>

declare const make: (
  executionStrategy?: ExecutionStrategy
) => Effect<CloseableScope>
// Effect.ts
declare const scopeWith: <A, E, R>(
    f: (scope: Scope) => Effect<A, E, R>
) => Effect<A, E, R | Scope>
Effect.scopeWith(scope => scope.close(Exit.void)) 
//Property 'close' does not exist on type 'Scope'.(2339)

16.2 From Scopes to Resources

// Effect.ts
declare const acquireRelease: <A, E, R, X, R2>(
    acquire: Effect<A, E, R>,
    release: (a: A, exit: Exit<unknown, unknown>) => Effect<X, never, R2>
): Effect<A, E, Scope | R | R2>
// Effect.ts
declare const scope: Effect<Scope, never, Scope>
const acquireRelease = <A, E, R, X, R2>(
  acquire: Effect<A, E, R>,
  release: (a: A) => Effect<X, never, R2>
) =>
  Effect.gen(function*() {
    const r = yield* Effect.context<R | R2>()
    const scope = yield* Effect.scope
    const a = yield* acquire
    yield* Scope.addFinalizer(scope, release(a).pipe(Effect.provide(r)))
    return a
  })
const acquireRelease = <A, E, R, X, R2>(
  acquire: Effect<A, E, R>,
  release: (a: A) => Effect<X, never, R2>
) =>
  Effect.uninterruptible(
    Effect.gen(function*() {
      const r = yield* Effect.context<R | R2>()
      const scope = yield* Effect.scope
      const a = yield* acquire
      yield* Scope.addFinalizer(scope, release(a).pipe(Effect.provide(r)))
      return a
    })
  )

16.3 Using Resources

declare const scoped: <A, E, R>(
    effect: Effect<A, E, R>
) => Effect<A, E, Exclude<R, Scope>>
declare const make: (
  executionStrategy?: ExecutionStrategy
) => Effect<CloseableScope>
const extend = <A, E, R>(effect: Effect<A, E, R>, scope: Scope) =>
  Effect.mapInputContext<A, E, R, Exclude<R, Scope>>(
    effect,
    // @ts-expect-error Type 'Context<Scope | Exclude<R, Scope>>' is not assignable to type 'Context<R>'
    (context) => Context.merge(context, Context.make(Scope, scope))
  )
const use = <A, E, R>(
  effect: Effect<A, E, R>,
  scope: Scope.CloseableScope
): Effect<A, E, Exclude<R, Scope.Scope>> =>
  Scope.extend(effect, scope).pipe(
    Effect.onExit((exit) => Scope.close(scope, exit))
  )
const scoped = <A, E, R>(effect: Effect<A, E, R>): Effect<A, E, Exclude<R, Scope.Scope>> =>
  Scope.make()
    .pipe(Effect.flatMap((scope) => use(effect, scope)))

16.4 Child Scopes

// Effect.ts
declare const withEarlyRelease: <A, E, R>(
  self: Effect<A, E, R>
) => Effect<[Effect<void>, A], E, R | Scope>
const withEarlyRelease = <A, E, R>(
  self: Effect<A, E, R>
): Effect<[Effect<void>, A], E, R | Scope.Scope> =>
  Effect.gen(function*() {
    const scope = yield* Effect.scope

    const release = Effect.fiberIdWith((id) =>
      // Argument of type 'Scope' is not assignable to parameter of type 'CloseableScope'.
      Scope.close(scope, Exit.interrupt(id))
    )
    const a = yield* self
    return [release, a]
  })
// https://effect.website/play#3be372e60594
const resource = (label: string) =>
  Effect.acquireRelease(
    Effect.log(`Acquiring ${label}`),
    () => Effect.log(`Releasing ${label}`)
  )

const program = Effect.gen(function*() {
  yield* resource("A")
  const [release, _] = yield* resource("B").pipe(withEarlyRelease)
  yield* release
  yield* Effect.log("Using A")
}).pipe(Effect.scoped)
// https://effect.website/play#2b5ebbf80519
const withEarlyRelease = <A, E, R>(
  self: Effect<A, E, R>
): Effect<[Effect<void>, A], E, R | Scope.Scope> =>
  Effect.gen(function*() {
    const parent = yield* Effect.scope
    const child = yield* Scope.fork(parent, ExecutionStrategy.sequential)

    const release = Effect.fiberIdWith((id) => Scope.close(child, Exit.interrupt(id)))
    const a = yield* Scope.extend(self, child) // typo in Zionomicon

    return [release, a]
  })

16.5 Putting it All Together

declare const memoize = <A, B, E, R>(
    f: (a: A) => Effect<B, E, R>
): Effect<(a: A) => Effect<B, E, R | Scope>, never, Scope>
const memoize = <A, B, E, R>(
    f: (a: A) => Effect<B, E, R>
): Effect<(a: A) => Effect<B, E, R | Scope>, never, Scope> = Effect.gen(function*() {
    const scope = yield* Effect.scope
    const ref = yield* Ref.make(HashMap.empty<A, Deferred<B, E>>()) 
    return // ???
})
// https://effect.website/play#549b50b3b269
const memoize = <A, B, E, R>(
  f: (a: A) => Effect<B, E, R | Scope.Scope>
) =>
  Effect.gen(function*() {
    const scope = yield* Effect.scope
    const ref = yield* Ref.make(HashMap.empty<A, Deferred.Deferred<B, E>>())
    return (a: A) =>
      Deferred.make<B, E>().pipe(Effect.flatMap((deferred) =>
        Ref.modify(ref, (map) =>
          HashMap.get(map, a).pipe(
            Option.match({
              onSome: (def) => [Deferred.await(def), map],
              onNone: () => [
                Scope.extend(f(a), scope).pipe(
                  Effect.either,
                  Effect.flatMap((x) => Deferred.complete(deferred, x)),
                  Effect.zipRight(Deferred.await(deferred))
                ),
                HashMap.set(map, a, deferred)
              ]
            })
          ))
      )).pipe(
        Effect.flatten
      )
  })