import * as E from "../../Either"; import { pipe } from "../../Function"; import type { AtomicReference } from "../../support"; import * as T from "../Task/_core"; import type { EIO, IO } from "../Task/model"; import { modify } from "./atomic"; export interface XRef { /** * Folds over the error and value types of the `XRef`. This is a highly * polymorphic method that is capable of arbitrarily transforming the error * and value types of the `XRef`. For most use cases one of the more specific * combinators implemented in terms of `fold` will be more ergonomic but this * method is extremely useful for implementing new combinators. */ readonly fold: ( ea: (_: EA) => EC, eb: (_: EB) => ED, ca: (_: C) => E.Either, bd: (_: B) => E.Either ) => XRef; /** * Folds over the error and value types ofthe `XRef`, allowing access to * the state in transforming the `set` value. This is a more powerful version * of `fold` but requires unifying the error types. */ readonly foldAll: ( ea: (_: EA) => EC, eb: (_: EB) => ED, ec: (_: EB) => EC, ca: (_: C) => (_: B) => E.Either, bd: (_: B) => E.Either ) => XRef; /** * Reads the value from the `XRef`. */ readonly get: EIO; /** * Writes a new value to the `XRef`, with a guarantee of immediate * consistency (at some cost to performance). */ readonly set: (a: A) => EIO; } export class DerivedAll implements XRef { readonly _tag = "DerivedAll"; constructor( readonly value: Atomic, readonly getEither: (s: S) => E.Either, readonly setEither: (a: A) => (s: S) => E.Either ) {} readonly fold = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ca: (_: C) => E.Either, bd: (_: B) => E.Either ): XRef => new DerivedAll( this.value, (s) => E.fold_(this.getEither(s), (e) => E.left(eb(e)), bd), (c) => (s) => E.chain_(ca(c), (a) => E.fold_(this.setEither(a)(s), (e) => E.left(ea(e)), E.right)) ); readonly foldAll = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ec: (_: EB) => EC, ca: (_: C) => (_: B) => E.Either, bd: (_: B) => E.Either ): XRef => new DerivedAll( this.value, (s) => E.fold_(this.getEither(s), (e) => E.left(eb(e)), bd), (c) => (s) => pipe( this.getEither(s), E.fold((e) => E.left(ec(e)), ca(c)), E.deunion, E.chain((a) => E.fold_(this.setEither(a)(s), (e) => E.left(ea(e)), E.right)) ) ); readonly get: EIO = pipe( this.value.get, T.chain((a) => E.fold_(this.getEither(a), T.fail, T.pure)) ); readonly set: (a: A) => EIO = (a) => pipe( this.value, modify((s) => E.fold_( this.setEither(a)(s), (e) => [E.left(e), s] as [E.Either, S], (s) => [E.right(undefined), s] as [E.Either, S] ) ), T.absolve ); } export class Derived implements XRef { readonly _tag = "Derived"; constructor( readonly value: Atomic, readonly getEither: (s: S) => E.Either, readonly setEither: (a: A) => E.Either ) {} readonly fold = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ca: (_: C) => E.Either, bd: (_: B) => E.Either ): XRef => new Derived( this.value, (s) => E.fold_(this.getEither(s), (e) => E.left(eb(e)), bd), (c) => E.chain_(ca(c), (a) => E.fold_(this.setEither(a), (e) => E.left(ea(e)), E.right)) ); readonly foldAll = ( ea: (_: EA) => EC, eb: (_: EB) => ED, ec: (_: EB) => EC, ca: (_: C) => (_: B) => E.Either, bd: (_: B) => E.Either ): XRef => new DerivedAll( this.value, (s) => E.fold_(this.getEither(s), (e) => E.left(eb(e)), E.right) as E.Either, (c) => (s) => pipe( this.getEither(s), E.fold((e) => E.left(ec(e)), ca(c)), E.deunion, E.chain((a) => pipe( this.setEither(a), E.fold((e) => E.left(ea(e)), E.right) ) ) ) ); readonly get: EIO = pipe( this.value.get, T.chain((s) => E.fold_(this.getEither(s), T.fail, T.pure)) ); readonly set: (a: A) => EIO = (a) => E.fold_(this.setEither(a), T.fail, this.value.set); } export class Atomic implements XRef { readonly _tag = "Atomic"; readonly fold = ( _ea: (_: never) => EC, _eb: (_: never) => ED, ca: (_: C) => E.Either, bd: (_: A) => E.Either ): XRef => new Derived( this, (s) => bd(s), (c) => ca(c) ); readonly foldAll = ( _ea: (_: never) => EC, _eb: (_: never) => ED, _ec: (_: never) => EC, ca: (_: C) => (_: A) => E.Either, bd: (_: A) => E.Either ): XRef => new DerivedAll( this, (s) => bd(s), (c) => (s) => ca(c)(s) ); constructor(readonly value: AtomicReference) {} get get(): IO { return T.total(() => this.value.get); } readonly set = (a: A): IO => { return T.total(() => { this.value.set(a); }); }; } /** * A Ref that can fail with error E */ export interface ERef extends XRef {} /** * A Ref that cannot fail */ export interface Ref extends ERef {} /** * Cast to a sealed union in case of ERef (where it make sense) */ export const concrete = (self: XRef) => self as Atomic | DerivedAll | Derived;