import type { Semaphore } from "../Semaphore"; import { withPermit } from "../Semaphore"; import * as T from "../Task/_core"; import type { Ref } from "../XRef"; /** * A `XRefM` is a polymorphic, purely functional * description of a mutable reference. The fundamental operations of a `XRefM` * are `set` and `get`. `set` takes a value of type `A` and sets the reference * to a new value, requiring an environment of type `RA` and potentially * failing with an error of type `EA`. `get` gets the current value of the * reference and returns a value of type `B`, requiring an environment of type * `RB` and potentially failing with an error of type `EB`. * * When the error and value types of the `XRefM` are unified, that is, it is a * `XRefM[E, E, A, A]`, the `XRefM` also supports atomic `modify` and `update` * operations. * * Unlike `ZRef`, `XRefM` allows performing effects within update operations, * at some cost to performance. Writes will semantically block other writers, *while multiple readers can read simultaneously. */ export interface XRefM { /** * Folds over the error and value types of the `XRefM`. This is a highly * polymorphic method that is capable of arbitrarily transforming the error * and value types of the `XRefM`. For most use cases one of the more * specific combinators implemented in terms of `foldM` will be more * ergonomic but this method is extremely useful for implementing new * combinators. */ readonly foldM: ( ea: (_: EA) => EC, eb: (_: EB) => ED, ca: (_: C) => T.Task, bd: (_: B) => T.Task ) => XRefM; /** * Folds over the error and value types of the `XRefM`, allowing access to * the state in transforming the `set` value. This is a more powerful version * of `foldM` but requires unifying the environment and error types. */ readonly foldAllM: ( ea: (_: EA) => EC, eb: (_: EB) => ED, ec: (_: EB) => EC, ca: (_: C) => (_: B) => T.Task, bd: (_: B) => T.Task ) => XRefM; /** * Reads the value from the `XRefM`. */ readonly get: T.Task; /** * Writes a new value to the `XRefM`, with a guarantee of immediate * consistency (at some cost to performance). */ readonly set: (a: A) => T.Task; } export class DerivedAll implements XRefM { readonly _tag = "DerivedAll"; constructor( readonly value: Atomic, readonly getEither: (s: S) => T.Task, readonly setEither: (a: A) => (s: S) => T.Task ) {} readonly foldM = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ca: (_: C) => T.Task, bd: (_: B) => T.Task ): XRefM => new DerivedAll( this.value, (s) => T.foldM_( this.getEither(s), (e) => T.fail(eb(e)), (a) => bd(a) ), (a) => (s) => T.chain_(ca(a), (a) => T.first_(this.setEither(a)(s), ea)) ); readonly foldAllM = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ec: (_: EB) => EC, ca: (_: C) => (_: B) => T.Task, bd: (_: B) => T.Task ): XRefM => new DerivedAll( this.value, (s) => T.foldM_( this.getEither(s), (e) => T.fail(eb(e)), (a) => bd(a) ), (c) => (s) => T.chain_( T.foldM_(this.getEither(s), (e) => T.fail(ec(e)), ca(c)), (a) => T.first_(this.setEither(a)(s), ea) ) ); get: T.Task = T.chain_(this.value.get, (a) => this.getEither(a)); set: (a: A) => T.Task = (a) => withPermit(this.value.semaphore)(T.chain_(T.chain_(this.value.get, this.setEither(a)), (a) => this.value.set(a))); } export class Derived implements XRefM { readonly _tag = "Derived"; constructor( readonly value: Atomic, readonly getEither: (s: S) => T.Task, readonly setEither: (a: A) => T.Task ) {} readonly foldM = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ca: (_: C) => T.Task, bd: (_: B) => T.Task ): XRefM => new Derived( this.value, (s) => T.foldM_( this.getEither(s), (e) => T.fail(eb(e)), (a) => bd(a) ), (a) => T.chain_(ca(a), (a) => T.first_(this.setEither(a), ea)) ); readonly foldAllM = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ec: (_: EB) => EC, ca: (_: C) => (_: B) => T.Task, bd: (_: B) => T.Task ): XRefM => new DerivedAll( this.value, (s) => T.foldM_( this.getEither(s), (e) => T.fail(eb(e)), (a) => bd(a) ), (c) => (s) => T.chain_( T.foldM_(this.getEither(s), (e) => T.fail(ec(e)), ca(c)), (a) => T.first_(this.setEither(a), ea) ) ); get: T.Task = T.chain_(this.value.get, (a) => this.getEither(a)); set: (a: A) => T.Task = (a) => withPermit(this.value.semaphore)(T.chain_(this.setEither(a), (a) => this.value.set(a))); } export class Atomic implements XRefM { readonly _tag = "Atomic"; constructor(readonly ref: Ref, readonly semaphore: Semaphore) {} readonly foldM = ( _ea: (_: never) => EC, _eb: (_: never) => ED, ca: (_: C) => T.Task, bd: (_: A) => T.Task ): XRefM => new Derived( this, (s) => bd(s), (a) => ca(a) ); readonly foldAllM = ( _ea: (_: never) => EC, _eb: (_: never) => ED, _ec: (_: never) => EC, ca: (_: C) => (_: A) => T.Task, bd: (_: A) => T.Task ): XRefM => new DerivedAll( this, (s) => bd(s), (a) => (s) => ca(a)(s) ); readonly get: T.Task = this.ref.get; readonly set: (a: A) => T.Task = (a) => withPermit(this.semaphore)(this.set(a)); } export interface RefMRE extends XRefM {} export interface RefME extends XRefM {} export interface RefMR extends XRefM {} export interface RefM extends XRefM {} export const concrete = (_: XRefM) => _ as Atomic | Derived | DerivedAll;