16. Resource Handling: Advanced Scopes
// 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
} )
)
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 ) ) )
// 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
)
} )