// ets_tracing: off import "../../Operator/index.js" import type { HashMap } from "../../Collections/Immutable/HashMap/index.js" import type * as T from "../../Effect/index.js" import * as E from "../../Either/index.js" import { identity } from "../../Function/index.js" import * as O from "../../Option/index.js" import { AtomicReference } from "../../Support/AtomicReference/index.js" import { STMEffect } from "../STM/_internal/primitives.js" import * as STM from "../STM/core.js" import { makeEntry } from "../STM/Entry/index.js" import type { Journal, Todo } from "../STM/Journal/index.js" import { emptyTodoMap } from "../STM/Journal/index.js" import type { TxnId } from "../STM/TxnId/index.js" import { Versioned } from "../STM/Versioned/index.js" export const TRefTypeId = Symbol() export type TRefTypeId = typeof TRefTypeId /** * A `XTRef` is a polymorphic, purely functional description of a * mutable reference that can be modified as part of a transactional effect. The * fundamental operations of a `XTRef` are `set` and `get`. `set` takes a value * of type `A` and transactionally sets the reference to a new value, potentially * failing with an error of type `EA`. `get` gets the current value of the reference * and returns a value of type `B`, potentially failing with an error of type `EB`. * * When the error and value types of the `XTRef` are unified, that is, it is a * `XTRef`, the `ZTRef` also supports atomic `modify` and `update` * operations. All operations are guaranteed to be executed transactionally. * * NOTE: While `XTRef` provides the transactional equivalent of a mutable reference, * the value inside the `XTRef` should be immutable. */ export interface XTRef { readonly _typeId: TRefTypeId readonly _EA: () => EA readonly _EB: () => EB readonly _A: (_: A) => void readonly _B: () => B fold( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ca: (c: C) => E.Either, bd: (b: B) => E.Either ): XTRef foldAll( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ec: (ea: EB) => EC, ca: (c: C) => (b: B) => E.Either, bd: (b: B) => E.Either ): XTRef readonly atomic: Atomic } export interface TRef extends XTRef {} export interface ETRef extends XTRef {} export class Atomic implements XTRef { readonly _typeId: TRefTypeId = TRefTypeId readonly _tag = "Atomic" readonly _EA!: () => never readonly _EB!: () => never readonly _A!: (_: A) => void readonly _B!: () => A readonly atomic: Atomic = this as Atomic constructor( public versioned: Versioned, readonly todo: AtomicReference> ) {} fold( _ea: (ea: never) => EC, _eb: (ea: never) => ED, ca: (c: C) => E.Either, bd: (b: A) => E.Either ): XTRef { return new Derived(bd, ca, this, this.atomic) } foldAll( _ea: (ea: never) => EC, _eb: (ea: never) => ED, _ec: (ea: never) => EC, ca: (c: C) => (b: A) => E.Either, bd: (b: A) => E.Either ): XTRef { return new DerivedAll(bd, ca, this, this.atomic) } } export class Derived implements XTRef { readonly _typeId: TRefTypeId = TRefTypeId readonly _tag = "Derived" readonly _EA!: () => EA readonly _EB!: () => EB readonly _A!: (_: A) => void readonly _B!: () => B constructor( readonly getEither: (s: S) => E.Either, readonly setEither: (a: A) => E.Either, readonly value: Atomic, readonly atomic: Atomic ) {} fold( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ca: (c: C) => E.Either, bd: (b: B) => E.Either ): XTRef { return new Derived( (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) ), this.value, this.atomic ) } foldAll( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ec: (ea: EB) => EC, ca: (c: C) => (b: B) => E.Either, bd: (b: B) => E.Either ): XTRef { return new DerivedAll( (s) => E.fold_(this.getEither(s), (e) => E.left(eb(e)), bd), (c) => (s) => E.chain_( E.fold_(this.getEither(s), (e) => E.left(ec(e)), ca(c)), (a) => E.fold_(this.setEither(a), (e) => E.left(ea(e)), E.right) ), this.value, this.atomic ) } } export class DerivedAll implements XTRef { readonly _typeId: TRefTypeId = TRefTypeId readonly _tag = "DerivedAll" readonly _EA!: () => EA readonly _EB!: () => EB readonly _A!: (_: A) => void readonly _B!: () => B constructor( readonly getEither: (s: S) => E.Either, readonly setEither: (a: A) => (s: S) => E.Either, readonly value: Atomic, readonly atomic: Atomic ) {} fold( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ca: (c: C) => E.Either, bd: (b: B) => E.Either ): XTRef { return new DerivedAll( (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) ), this.value, this.atomic ) } foldAll( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ec: (ea: EB) => EC, ca: (c: C) => (b: B) => E.Either, bd: (b: B) => E.Either ): XTRef { return new DerivedAll( (s) => E.fold_(this.getEither(s), (e) => E.left(eb(e)), bd), (c) => (s) => E.chain_( E.fold_(this.getEither(s), (e) => E.left(ec(e)), ca(c)), (a) => E.fold_(this.setEither(a)(s), (e) => E.left(ea(e)), E.right) ), this.value, this.atomic ) } } function getOrMakeEntry(self: Atomic, journal: Journal) { if (journal.has(self)) { return journal.get(self)! } const entry = makeEntry(self, false) journal.set(self, entry) return entry } /** * Retrieves the value of the `XTRef`. */ export function get(self: XTRef): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) return entry.use((_) => _.unsafeGet()) }) } case "Derived": { return STM.chain_(get(self.value), (s) => E.fold_(self.getEither(s), STM.fail, STM.succeed) ) } case "DerivedAll": { return STM.chain_(get(self.value), (s) => E.fold_(self.getEither(s), STM.fail, STM.succeed) ) } } } /** * Unsafely retrieves the value of the `XTRef`. */ export function unsafeGet_( self: XTRef, journal: Journal ): A { return getOrMakeEntry(self.atomic, journal).use((_) => _.unsafeGet()) } /** * Sets the value of the `XTRef`. */ export function set_( self: XTRef, a: A ): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) return entry.use((_) => _.unsafeSet(a)) }) } case "Derived": { return E.fold_(self.setEither(a), STM.fail, (s) => set_(self.value, s)) } case "DerivedAll": { return STM.absolve( modify_(self.value, (s) => E.fold_( self.setEither(a)(s), (e) => [E.leftW(e), s], (s) => [E.right(undefined), s] ) ) ) } } } /** * Updates the value of the variable, returning a function of the specified * value. */ export function modify_( self: ETRef, f: (a: A) => readonly [B, A] ): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) const oldValue = entry.use((_) => _.unsafeGet()) const [retValue, newValue] = f(oldValue) entry.use((_) => _.unsafeSet(newValue)) return retValue }) } case "Derived": { return STM.absolve( modify_(self.value, (s) => E.fold_( self.getEither(s), (e) => [E.leftW(e), s], (a1) => { const [b, a2] = f(a1) return E.fold_( self.setEither(a2), (e) => [E.left(e), s], (s) => [E.right(b), s] ) } ) ) ) } case "DerivedAll": { return STM.absolve( modify_(self.value, (s) => E.fold_( self.getEither(s), (e) => [E.leftW(e), s], (a1) => { const [b, a2] = f(a1) return E.fold_( self.setEither(a2)(s), (e) => [E.left(e), s], (s) => [E.right(b), s] ) } ) ) ) } } } /** * Updates the value of the variable, returning a function of the specified * value. * * @ets_data_first modify_ */ export function modify( f: (a: A) => readonly [B, A] ): (self: ETRef) => STM.STM { return (self) => modify_(self, f) } /** * Updates the value of the variable, returning a function of the specified * value. */ export function modifySome_( self: ETRef, b: B, f: (a: A) => O.Option ): STM.STM { return modify_(self, (a) => O.fold_(f(a), () => [b, a], identity)) } /** * Updates the value of the variable, returning a function of the specified * value. * * @ets_data_first modifySome_ */ export function modifySome( b: B, f: (a: A) => O.Option ): (self: ETRef) => STM.STM { return (self) => modifySome_(self, b, f) } /** * Sets the value of the `XTRef` and returns the old value. */ export function getAndSet_(self: ETRef, a: A): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) const oldValue = entry.use((_) => _.unsafeGet()) entry.use((_) => _.unsafeSet(a)) return oldValue }) } default: { return modify_(self, (_) => [_, a]) } } } /** * Sets the value of the `XTRef` and returns the old value. * * @ets_data_first getAndSet_ */ export function getAndSet( a: A ): (self: ETRef) => STM.STM { return (self) => getAndSet_(self, a) } /** * Updates the value of the variable and returns the old value. */ export function getAndUpdate_( self: ETRef, f: (a: A) => A ): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) const oldValue = entry.use((_) => _.unsafeGet()) entry.use((_) => _.unsafeSet(f(oldValue))) return oldValue }) } default: { return modify_(self, (_) => [_, f(_)]) } } } /** * Updates the value of the variable and returns the old value. * * @ets_data_first getAndUpdate_ */ export function getAndUpdate( f: (a: A) => A ): (self: ETRef) => STM.STM { return (self) => getAndUpdate_(self, f) } /** * Updates some values of the variable but leaves others alone, returning the * old value. */ export function getAndUpdateSome_( self: ETRef, f: (a: A) => O.Option ): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) const oldValue = entry.use((_) => _.unsafeGet()) const v = f(oldValue) if (O.isSome(v)) { entry.use((_) => _.unsafeSet(v.value)) } return oldValue }) } default: { return modify_(self, (_) => O.fold_( f(_), () => [_, _], (v) => [_, v] ) ) } } } /** * Updates some values of the variable but leaves others alone, returning the * old value. * * @ets_data_first getAndUpdateSome_ */ export function getAndUpdateSome( f: (a: A) => O.Option ): (self: ETRef) => STM.STM { return (self) => getAndUpdateSome_(self, f) } /** * Sets the value of the `XTRef`. * * @ets_data_first set_ */ export function set( a: A ): (self: XTRef) => STM.STM { return (self) => set_(self, a) } /** * Updates the value of the variable. */ export function update_( self: ETRef, f: (a: A) => A ): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) const newValue = f(entry.use((_) => _.unsafeGet())) entry.use((_) => _.unsafeSet(newValue)) }) } default: return modify_(self, (a) => [undefined, f(a)]) } } /** * Updates the value of the variable. * * @ets_data_first update_ */ export function update( f: (a: A) => A ): (self: ETRef) => STM.STM { return (self) => update_(self, f) } /** * Updates some values of the variable but leaves others alone. */ export function updateSome_( self: ETRef, f: (a: A) => O.Option ): STM.STM { return update_(self, (a) => O.fold_(f(a), () => a, identity)) } /** * Updates some values of the variable but leaves others alone. * * @ets_data_first updateSome_ */ export function updateSome( f: (a: A) => O.Option ): (self: ETRef) => STM.STM { return (self) => updateSome_(self, f) } /** * Updates some values of the variable but leaves others alone. */ export function updateSomeAndGet_( self: ETRef, f: (a: A) => O.Option ): STM.STM { return updateAndGet_(self, (a) => O.fold_(f(a), () => a, identity)) } /** * Updates some values of the variable but leaves others alone. * * @ets_data_first updateSomeAndGet_ */ export function updateSomeAndGet( f: (a: A) => O.Option ): (self: ETRef) => STM.STM { return (self) => updateSomeAndGet_(self, f) } /** * Updates the value of the variable and returns the new value. */ export function updateAndGet_( self: ETRef, f: (a: A) => A ): STM.STM { concrete(self) switch (self._tag) { case "Atomic": { return new STMEffect((journal) => { const entry = getOrMakeEntry(self, journal) const oldValue = entry.use((_) => _.unsafeGet()) const x = f(oldValue) entry.use((_) => _.unsafeSet(x)) return x }) } default: { return modify_(self, (_) => { const x = f(_) return [x, x] }) } } } /** * Updates the value of the variable and returns the new value. * * @ets_data_first getAndUpdate_ */ export function updateAndGet( f: (a: A) => A ): (self: ETRef) => STM.STM { return (self) => updateAndGet_(self, f) } /** * @ets_optimize remove */ export function concrete( _: XTRef ): asserts _ is | Atomic | Derived | DerivedAll { // } /** * Makes a new `XTRef` that is initialized to the specified value. */ export function makeWith(a: () => A): STM.STM> { return new STMEffect((journal) => { const value = a() const versioned = new Versioned(value) const todo = new AtomicReference(emptyTodoMap) const tref = new Atomic(versioned, todo) journal.set(tref, makeEntry(tref, true)) return tref }) } /** * Makes a new `XTRef` that is initialized to the specified value. */ export function make(a: A): STM.STM> { return new STMEffect((journal) => { const value = a const versioned = new Versioned(value) const todo = new AtomicReference(emptyTodoMap) const tref = new Atomic(versioned, todo) journal.set(tref, makeEntry(tref, true)) return tref }) } /** * Unsafely makes a new `XTRef` that is initialized to the specified value. */ export function unsafeMake(a: A): TRef { const value = a const versioned = new Versioned(value) const todo = new AtomicReference(emptyTodoMap) return new Atomic(versioned, todo) } /** * Makes a new `XTRef` that is initialized to the specified value. */ export function makeCommitWith(a: () => A): T.UIO> { return STM.commit(makeWith(a)) } /** * Makes a new `XTRef` that is initialized to the specified value. */ export function makeCommit(a: A): T.UIO> { return STM.commit(make(a)) } /** * Folds over the error and value types of the `XTRef`. This is a highly * polymorphic method that is capable of arbitrarily transforming the error * and value types of the `XTRef`. 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. */ export function fold_( self: XTRef, ea: (ea: EA) => EC, eb: (ea: EB) => ED, ca: (c: C) => E.Either, bd: (b: B) => E.Either ): XTRef { return self.fold(ea, eb, ca, bd) } /** * Folds over the error and value types of the `XTRef`. This is a highly * polymorphic method that is capable of arbitrarily transforming the error * and value types of the `XTRef`. 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. * * @ets_data_first fold_ */ export function fold( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ca: (c: C) => E.Either, bd: (b: B) => E.Either ): (self: XTRef) => XTRef { return (self) => fold_(self, ea, eb, ca, bd) } /** * Folds over the error and value types of the `XTRef`, allowing access to * the state in transforming the `set` value. This is a more powerful version * of `fold` but requires unifying the error types. */ export function foldAll_( self: XTRef, ea: (ea: EA) => EC, eb: (ea: EB) => ED, ec: (ea: EB) => EC, ca: (c: C) => (b: B) => E.Either, bd: (b: B) => E.Either ): XTRef { return self.foldAll(ea, eb, ec, ca, bd) } /** * Folds over the error and value types of the `XTRef`, allowing access to * the state in transforming the `set` value. This is a more powerful version * of `fold` but requires unifying the error types. * * @ets_data_first foldAll_ */ export function foldAll( ea: (ea: EA) => EC, eb: (ea: EB) => ED, ec: (ea: EB) => EC, ca: (c: C) => (b: B) => E.Either, bd: (b: B) => E.Either ): (self: XTRef) => XTRef { return (self) => self.foldAll(ea, eb, ec, ca, bd) }