@effect-ts/system
Version:
Effect-TS is a zero dependency set of libraries to write highly productive, purely functional TypeScript at scale.
738 lines (684 loc) • 19.9 kB
text/typescript
// 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<EA, EB, A, B>` 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<E, E, A, A>`, 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<EA, EB, A, B> {
readonly _typeId: TRefTypeId
readonly _EA: () => EA
readonly _EB: () => EB
readonly _A: (_: A) => void
readonly _B: () => B
fold<EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ca: (c: C) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D>
foldAll<EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ec: (ea: EB) => EC,
ca: (c: C) => (b: B) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D>
readonly atomic: Atomic<unknown>
}
export interface TRef<A> extends XTRef<never, never, A, A> {}
export interface ETRef<E, A> extends XTRef<E, E, A, A> {}
export class Atomic<A> implements XTRef<never, never, A, A> {
readonly _typeId: TRefTypeId = TRefTypeId
readonly _tag = "Atomic"
readonly _EA!: () => never
readonly _EB!: () => never
readonly _A!: (_: A) => void
readonly _B!: () => A
readonly atomic: Atomic<unknown> = this as Atomic<unknown>
constructor(
public versioned: Versioned<A>,
readonly todo: AtomicReference<HashMap<TxnId, Todo>>
) {}
fold<EC, ED, C, D>(
_ea: (ea: never) => EC,
_eb: (ea: never) => ED,
ca: (c: C) => E.Either<EC, A>,
bd: (b: A) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
return new Derived(bd, ca, this, this.atomic)
}
foldAll<EC, ED, C, D>(
_ea: (ea: never) => EC,
_eb: (ea: never) => ED,
_ec: (ea: never) => EC,
ca: (c: C) => (b: A) => E.Either<EC, A>,
bd: (b: A) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
return new DerivedAll(bd, ca, this, this.atomic)
}
}
export class Derived<S, EA, EB, A, B> implements XTRef<EA, EB, A, B> {
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<EB, B>,
readonly setEither: (a: A) => E.Either<EA, S>,
readonly value: Atomic<S>,
readonly atomic: Atomic<unknown>
) {}
fold<EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ca: (c: C) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
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<EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ec: (ea: EB) => EC,
ca: (c: C) => (b: B) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
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<S, EA, EB, A, B> implements XTRef<EA, EB, A, B> {
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<EB, B>,
readonly setEither: (a: A) => (s: S) => E.Either<EA, S>,
readonly value: Atomic<S>,
readonly atomic: Atomic<unknown>
) {}
fold<EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ca: (c: C) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
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<EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ec: (ea: EB) => EC,
ca: (c: C) => (b: B) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
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<A>(self: Atomic<A>, 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<EA, EB, A, B>(self: XTRef<EA, EB, A, B>): STM.STM<unknown, EB, B> {
concrete(self)
switch (self._tag) {
case "Atomic": {
return new STMEffect((journal) => {
const entry = getOrMakeEntry(self, journal)
return entry.use((_) => _.unsafeGet<B>())
})
}
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_<EA, EB, A, B>(
self: XTRef<EA, EB, A, B>,
journal: Journal
): A {
return getOrMakeEntry(self.atomic, journal).use((_) => _.unsafeGet<A>())
}
/**
* Sets the value of the `XTRef`.
*/
export function set_<EA, EB, A, B>(
self: XTRef<EA, EB, A, B>,
a: A
): STM.STM<unknown, EA, void> {
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_<E, A, B>(
self: ETRef<E, A>,
f: (a: A) => readonly [B, A]
): STM.STM<unknown, E, B> {
concrete(self)
switch (self._tag) {
case "Atomic": {
return new STMEffect((journal) => {
const entry = getOrMakeEntry(self, journal)
const oldValue = entry.use((_) => _.unsafeGet<A>())
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, B>(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, B>(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<A, B>(
f: (a: A) => readonly [B, A]
): <E>(self: ETRef<E, A>) => STM.STM<unknown, E, B> {
return (self) => modify_(self, f)
}
/**
* Updates the value of the variable, returning a function of the specified
* value.
*/
export function modifySome_<E, A, B>(
self: ETRef<E, A>,
b: B,
f: (a: A) => O.Option<readonly [B, A]>
): STM.STM<unknown, E, B> {
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<A, B>(
b: B,
f: (a: A) => O.Option<readonly [B, A]>
): <E>(self: ETRef<E, A>) => STM.STM<unknown, E, B> {
return (self) => modifySome_(self, b, f)
}
/**
* Sets the value of the `XTRef` and returns the old value.
*/
export function getAndSet_<EA, A>(self: ETRef<EA, A>, a: A): STM.STM<unknown, EA, A> {
concrete(self)
switch (self._tag) {
case "Atomic": {
return new STMEffect((journal) => {
const entry = getOrMakeEntry(self, journal)
const oldValue = entry.use((_) => _.unsafeGet<A>())
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: A
): <EA>(self: ETRef<EA, A>) => STM.STM<unknown, EA, A> {
return (self) => getAndSet_(self, a)
}
/**
* Updates the value of the variable and returns the old value.
*/
export function getAndUpdate_<EA, A>(
self: ETRef<EA, A>,
f: (a: A) => A
): STM.STM<unknown, EA, A> {
concrete(self)
switch (self._tag) {
case "Atomic": {
return new STMEffect((journal) => {
const entry = getOrMakeEntry(self, journal)
const oldValue = entry.use((_) => _.unsafeGet<A>())
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<A>(
f: (a: A) => A
): <EA>(self: ETRef<EA, A>) => STM.STM<unknown, EA, A> {
return (self) => getAndUpdate_(self, f)
}
/**
* Updates some values of the variable but leaves others alone, returning the
* old value.
*/
export function getAndUpdateSome_<EA, A>(
self: ETRef<EA, A>,
f: (a: A) => O.Option<A>
): STM.STM<unknown, EA, A> {
concrete(self)
switch (self._tag) {
case "Atomic": {
return new STMEffect((journal) => {
const entry = getOrMakeEntry(self, journal)
const oldValue = entry.use((_) => _.unsafeGet<A>())
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<A>(
f: (a: A) => O.Option<A>
): <EA>(self: ETRef<EA, A>) => STM.STM<unknown, EA, A> {
return (self) => getAndUpdateSome_(self, f)
}
/**
* Sets the value of the `XTRef`.
*
* @ets_data_first set_
*/
export function set<A>(
a: A
): <EA, EB, B>(self: XTRef<EA, EB, A, B>) => STM.STM<unknown, EA, void> {
return (self) => set_(self, a)
}
/**
* Updates the value of the variable.
*/
export function update_<E, A>(
self: ETRef<E, A>,
f: (a: A) => A
): STM.STM<unknown, E, void> {
concrete(self)
switch (self._tag) {
case "Atomic": {
return new STMEffect((journal) => {
const entry = getOrMakeEntry(self, journal)
const newValue = f(entry.use((_) => _.unsafeGet<A>()))
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<A>(
f: (a: A) => A
): <E>(self: ETRef<E, A>) => STM.STM<unknown, E, void> {
return (self) => update_(self, f)
}
/**
* Updates some values of the variable but leaves others alone.
*/
export function updateSome_<E, A>(
self: ETRef<E, A>,
f: (a: A) => O.Option<A>
): STM.STM<unknown, E, void> {
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<A>(
f: (a: A) => O.Option<A>
): <E>(self: ETRef<E, A>) => STM.STM<unknown, E, void> {
return (self) => updateSome_(self, f)
}
/**
* Updates some values of the variable but leaves others alone.
*/
export function updateSomeAndGet_<E, A>(
self: ETRef<E, A>,
f: (a: A) => O.Option<A>
): STM.STM<unknown, E, A> {
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<A>(
f: (a: A) => O.Option<A>
): <E>(self: ETRef<E, A>) => STM.STM<unknown, E, A> {
return (self) => updateSomeAndGet_(self, f)
}
/**
* Updates the value of the variable and returns the new value.
*/
export function updateAndGet_<EA, A>(
self: ETRef<EA, A>,
f: (a: A) => A
): STM.STM<unknown, EA, A> {
concrete(self)
switch (self._tag) {
case "Atomic": {
return new STMEffect((journal) => {
const entry = getOrMakeEntry(self, journal)
const oldValue = entry.use((_) => _.unsafeGet<A>())
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<A>(
f: (a: A) => A
): <EA>(self: ETRef<EA, A>) => STM.STM<unknown, EA, A> {
return (self) => updateAndGet_(self, f)
}
/**
* @ets_optimize remove
*/
export function concrete<EA, EB, A, B>(
_: XTRef<EA, EB, A, B>
): asserts _ is
| Atomic<any>
| Derived<any, any, any, any, any>
| DerivedAll<any, any, any, any, any> {
//
}
/**
* Makes a new `XTRef` that is initialized to the specified value.
*/
export function makeWith<A>(a: () => A): STM.STM<unknown, never, TRef<A>> {
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: A): STM.STM<unknown, never, TRef<A>> {
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: A): TRef<A> {
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: () => A): T.UIO<TRef<A>> {
return STM.commit(makeWith(a))
}
/**
* Makes a new `XTRef` that is initialized to the specified value.
*/
export function makeCommit<A>(a: A): T.UIO<TRef<A>> {
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_<EA, EB, A, B, EC, ED, C, D>(
self: XTRef<EA, EB, A, B>,
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ca: (c: C) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
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, EB, A, B, EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ca: (c: C) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): (self: XTRef<EA, EB, A, B>) => XTRef<EC, ED, C, D> {
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_<EA, EB, A, B, EC, ED, C, D>(
self: XTRef<EA, EB, A, B>,
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ec: (ea: EB) => EC,
ca: (c: C) => (b: B) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): XTRef<EC, ED, C, D> {
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, EB, A, B, EC, ED, C, D>(
ea: (ea: EA) => EC,
eb: (ea: EB) => ED,
ec: (ea: EB) => EC,
ca: (c: C) => (b: B) => E.Either<EC, A>,
bd: (b: B) => E.Either<ED, D>
): (self: XTRef<EA, EB, A, B>) => XTRef<EC, ED, C, D> {
return (self) => self.foldAll(ea, eb, ec, ca, bd)
}