802 lines
22 KiB
TypeScript
802 lines
22 KiB
TypeScript
/**
|
|
* Stores fiber-safe mutable state inside Effect programs.
|
|
*
|
|
* A `Ref<A>` holds one value and exposes reads, writes, and atomic
|
|
* transformations as effects, so state changes compose with Effect's
|
|
* concurrency model. This module includes constructors, safe and unsafe reads,
|
|
* set and get-and-set helpers, update and modify helpers, and conditional
|
|
* update variants that leave the value unchanged when an `Option.none` result
|
|
* is returned.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
import * as Effect from "./Effect.ts"
|
|
import { dual, identity } from "./Function.ts"
|
|
import { PipeInspectableProto } from "./internal/core.ts"
|
|
import * as MutableRef from "./MutableRef.ts"
|
|
import type * as Option from "./Option.ts"
|
|
import type { Pipeable } from "./Pipeable.ts"
|
|
import type { Invariant } from "./Types.ts"
|
|
|
|
const TypeId = "~effect/Ref"
|
|
|
|
/**
|
|
* A mutable reference that provides atomic read, write, and update operations.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to keep shared mutable state that is read and updated inside Effect
|
|
* programs.
|
|
*
|
|
* **Details**
|
|
*
|
|
* A `Ref` is a thread-safe mutable reference type for shared state. It supports
|
|
* simple read and write operations as well as atomic transformations.
|
|
*
|
|
* **Example** (Reading and updating a ref)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* // Create a ref with initial value
|
|
* const counter = yield* Ref.make(0)
|
|
*
|
|
* // Read the current value
|
|
* const value = yield* Ref.get(counter)
|
|
* console.log(value) // 0
|
|
*
|
|
* // Update the value atomically
|
|
* yield* Ref.update(counter, (n) => n + 1)
|
|
*
|
|
* // Read the updated value
|
|
* const newValue = yield* Ref.get(counter)
|
|
* console.log(newValue) // 1
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link make} for creating a `Ref`
|
|
* @see {@link get} for reading the current value
|
|
* @see {@link set} for replacing the current value
|
|
*
|
|
* @category models
|
|
* @since 2.0.0
|
|
*/
|
|
export interface Ref<in out A> extends Ref.Variance<A>, Pipeable {
|
|
readonly ref: MutableRef.MutableRef<A>
|
|
}
|
|
|
|
/**
|
|
* The Ref namespace containing type definitions and utilities.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when referring to type members nested under the `Ref` namespace.
|
|
*
|
|
* @since 2.0.0
|
|
*/
|
|
export declare namespace Ref {
|
|
/**
|
|
* Variance interface for Ref types, defining the type parameter constraints.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when working with the type-level variance marker carried by `Ref`.
|
|
*
|
|
* **Example** (Using invariant refs)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* // This interface defines the invariant nature of Ref's type parameter
|
|
* // A Ref<A> is both a producer and consumer of A
|
|
* const program = Effect.gen(function*() {
|
|
* const ref = yield* Ref.make(42)
|
|
*
|
|
* // Ref is invariant - it can both produce and consume numbers
|
|
* const value = yield* Ref.get(ref) // produces number
|
|
* yield* Ref.set(ref, value + 1) // consumes number
|
|
* })
|
|
* ```
|
|
*
|
|
* @category models
|
|
* @since 2.0.0
|
|
*/
|
|
export interface Variance<in out A> {
|
|
readonly [TypeId]: {
|
|
readonly _A: Invariant<A>
|
|
}
|
|
}
|
|
}
|
|
|
|
const RefProto = {
|
|
[TypeId]: {
|
|
_A: identity
|
|
},
|
|
...PipeInspectableProto,
|
|
toJSON(this: Ref<any>) {
|
|
return {
|
|
_id: "Ref",
|
|
ref: this.ref
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new Ref with the specified initial value (unsafe version).
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when you need immediate synchronous construction and can guarantee
|
|
* that creating the `Ref` outside of `Effect` is safe.
|
|
*
|
|
* **Gotchas**
|
|
*
|
|
* Prefer `Ref.make` for Effect-wrapped creation in Effect programs.
|
|
*
|
|
* **Example** (Creating a ref unsafely)
|
|
*
|
|
* ```ts
|
|
* import { Ref } from "effect"
|
|
*
|
|
* // Create a ref directly without Effect
|
|
* const counter = Ref.makeUnsafe(0)
|
|
*
|
|
* // Get the current value
|
|
* const value = Ref.getUnsafe(counter)
|
|
* console.log(value) // 0
|
|
*
|
|
* // Note: This is unsafe and should be used carefully
|
|
* // Prefer Ref.make for Effect-wrapped creation
|
|
* ```
|
|
*
|
|
* @category constructors
|
|
* @since 4.0.0
|
|
*/
|
|
export const makeUnsafe = <A>(value: A): Ref<A> => {
|
|
const self = Object.create(RefProto)
|
|
self.ref = MutableRef.make(value)
|
|
return self
|
|
}
|
|
|
|
/**
|
|
* Creates a new Ref with the specified initial value.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to create a `Ref` for shared mutable state inside an Effect program.
|
|
*
|
|
* **Example** (Creating a ref)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const ref = yield* Ref.make(42)
|
|
* const value = yield* Ref.get(ref)
|
|
* console.log(value) // 42
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link makeUnsafe} for synchronous construction outside Effect code
|
|
*
|
|
* @category constructors
|
|
* @since 2.0.0
|
|
*/
|
|
export const make = <A>(value: A): Effect.Effect<Ref<A>> => Effect.sync(() => makeUnsafe(value))
|
|
|
|
/**
|
|
* Gets the current value of the Ref.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to read the current `Ref` value without changing it.
|
|
*
|
|
* **Example** (Getting the current value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const ref = yield* Ref.make(42)
|
|
* const value = yield* Ref.get(ref)
|
|
* console.log(value) // 42
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link set} for replacing the current value
|
|
*
|
|
* @category getters
|
|
* @since 2.0.0
|
|
*/
|
|
export const get = <A>(self: Ref<A>) => Effect.sync(() => self.ref.current)
|
|
|
|
/**
|
|
* Sets the value of the Ref to the specified value.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to replace the current `Ref` value with a known value.
|
|
*
|
|
* **Example** (Setting a value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const ref = yield* Ref.make(0)
|
|
* yield* Ref.set(ref, 42)
|
|
* const value = yield* Ref.get(ref)
|
|
* console.log(value) // 42
|
|
* })
|
|
*
|
|
* // Using multiple operations
|
|
* const program2 = Effect.gen(function*() {
|
|
* const ref = yield* Ref.make(0)
|
|
* yield* Ref.set(ref, 100)
|
|
* const value = yield* Ref.get(ref)
|
|
* console.log(value) // 100
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link getAndSet} for setting while returning the previous value
|
|
* @see {@link setAndGet} for setting while returning the new value
|
|
*
|
|
* @category setters
|
|
* @since 2.0.0
|
|
*/
|
|
export const set = dual<
|
|
<A>(value: A) => (self: Ref<A>) => Effect.Effect<void>,
|
|
<A>(self: Ref<A>, value: A) => Effect.Effect<void>
|
|
>(2, <A>(self: Ref<A>, value: A) => Effect.sync(() => MutableRef.set(self.ref, value)))
|
|
|
|
/**
|
|
* Gets the current value of the Ref, sets it to the specified value, and returns the previous value atomically.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to replace a plain `Ref` value while returning the previous value.
|
|
*
|
|
* **Example** (Replacing a value atomically)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const ref = yield* Ref.make("initial")
|
|
*
|
|
* // Get current value and set new value atomically
|
|
* const previous = yield* Ref.getAndSet(ref, "updated")
|
|
* console.log(previous) // "initial"
|
|
*
|
|
* const current = yield* Ref.get(ref)
|
|
* console.log(current) // "updated"
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link set} for setting without returning the previous value
|
|
* @see {@link getAndUpdate} for deriving the new value from the previous value
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const getAndSet = dual<
|
|
<A>(value: A) => (self: Ref<A>) => Effect.Effect<A>,
|
|
<A>(self: Ref<A>, value: A) => Effect.Effect<A>
|
|
>(2, <A>(self: Ref<A>, value: A) =>
|
|
Effect.sync(() => {
|
|
const current = self.ref.current
|
|
self.ref.current = value
|
|
return current
|
|
}))
|
|
|
|
/**
|
|
* Gets the current value of the Ref, updates it with the given function, and returns the previous value atomically.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to derive a new `Ref` value while returning the previous value.
|
|
*
|
|
* **Example** (Updating and returning the previous value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(10)
|
|
*
|
|
* // Get current value and update it atomically
|
|
* const previous = yield* Ref.getAndUpdate(counter, (n) => n * 2)
|
|
* console.log(previous) // 10
|
|
*
|
|
* const current = yield* Ref.get(counter)
|
|
* console.log(current) // 20
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link update} for updating without returning the previous value
|
|
* @see {@link updateAndGet} for returning the new value instead
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const getAndUpdate = dual<
|
|
<A>(f: (a: A) => A) => (self: Ref<A>) => Effect.Effect<A>,
|
|
<A>(self: Ref<A>, f: (a: A) => A) => Effect.Effect<A>
|
|
>(2, <A>(self: Ref<A>, f: (a: A) => A) =>
|
|
Effect.sync(() => {
|
|
const current = self.ref.current
|
|
self.ref.current = f(current)
|
|
return current
|
|
}))
|
|
|
|
/**
|
|
* Gets the current value of the Ref and updates it atomically with the given partial function.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to return the previous `Ref` value while applying a conditional update.
|
|
*
|
|
* **Details**
|
|
*
|
|
* If the partial function returns `Option.some`, the Ref is updated with the
|
|
* new value. If it returns `Option.none`, the Ref is left unchanged. The effect
|
|
* always returns the value that was in the Ref before the attempted update.
|
|
*
|
|
* **Example** (Conditionally updating and returning the previous value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Option, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(5)
|
|
*
|
|
* // Only update if value is greater than 3
|
|
* const previous1 = yield* Ref.getAndUpdateSome(
|
|
* counter,
|
|
* (n) => n > 3 ? Option.some(n * 2) : Option.none()
|
|
* )
|
|
* console.log(previous1) // 5
|
|
*
|
|
* const current1 = yield* Ref.get(counter)
|
|
* console.log(current1) // 10
|
|
*
|
|
* // Try to update again (won't update since 10 > 3 is true but let's say condition is n < 3)
|
|
* const previous2 = yield* Ref.getAndUpdateSome(
|
|
* counter,
|
|
* (n) => n < 3 ? Option.some(n * 2) : Option.none()
|
|
* )
|
|
* console.log(previous2) // 10
|
|
*
|
|
* const current2 = yield* Ref.get(counter)
|
|
* console.log(current2) // 10 (unchanged)
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link getAndUpdate} for always applying an update
|
|
* @see {@link updateSome} for conditional updates without returning the previous value
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const getAndUpdateSome = dual<
|
|
<A>(pf: (a: A) => Option.Option<A>) => (self: Ref<A>) => Effect.Effect<A>,
|
|
<A>(self: Ref<A>, pf: (a: A) => Option.Option<A>) => Effect.Effect<A>
|
|
>(2, <A>(self: Ref<A>, pf: (a: A) => Option.Option<A>) =>
|
|
Effect.sync(() => {
|
|
const current = self.ref.current
|
|
const option = pf(current)
|
|
if (option._tag === "Some") {
|
|
self.ref.current = option.value
|
|
}
|
|
return current
|
|
}))
|
|
|
|
/**
|
|
* Sets the value of the Ref atomically to the specified value and returns the new value.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when you want to set a `Ref` value and immediately get it back in one
|
|
* atomic operation.
|
|
*
|
|
* **Example** (Setting and returning the new value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const ref = yield* Ref.make(10)
|
|
*
|
|
* // Set new value and get it back in one operation
|
|
* const newValue = yield* Ref.setAndGet(ref, 42)
|
|
* console.log(newValue) // 42
|
|
*
|
|
* // Verify the ref contains the new value
|
|
* const current = yield* Ref.get(ref)
|
|
* console.log(current) // 42
|
|
* })
|
|
*
|
|
* // Useful for sequential operations
|
|
* const program2 = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(0)
|
|
*
|
|
* const newValue = yield* Ref.setAndGet(counter, 20)
|
|
* console.log(newValue) // 20
|
|
* })
|
|
* ```
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const setAndGet = dual<
|
|
<A>(value: A) => (self: Ref<A>) => Effect.Effect<A>,
|
|
<A>(self: Ref<A>, value: A) => Effect.Effect<A>
|
|
>(2, <A>(self: Ref<A>, value: A) => Effect.sync(() => self.ref.current = value))
|
|
|
|
/**
|
|
* Modifies the value of the Ref atomically using the given function.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to compute both a separate return value and the next stored `Ref` value
|
|
* in one atomic update.
|
|
*
|
|
* **Details**
|
|
*
|
|
* The function receives the current value and returns a tuple of
|
|
* `[result, newValue]`. The Ref is updated with `newValue`, and `result` is
|
|
* returned by the effect.
|
|
*
|
|
* **Example** (Modifying a value atomically)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(10)
|
|
*
|
|
* // Modify the ref and return some computation result
|
|
* const result = yield* Ref.modify(counter, (n) => [
|
|
* `Previous value was ${n}`, // Return value
|
|
* n * 2 // New ref value
|
|
* ])
|
|
*
|
|
* console.log(result) // "Previous value was 10"
|
|
*
|
|
* const current = yield* Ref.get(counter)
|
|
* console.log(current) // 20
|
|
* })
|
|
*
|
|
* // Example with more complex computation
|
|
* const program2 = Effect.gen(function*() {
|
|
* const state = yield* Ref.make({ count: 0, total: 0 })
|
|
*
|
|
* const incremented = yield* Ref.modify(state, (s) => [
|
|
* s.count, // Return previous count
|
|
* { count: s.count + 1, total: s.total + s.count + 1 } // New state
|
|
* ])
|
|
*
|
|
* console.log(incremented) // 0
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link updateAndGet} for returning the new stored value
|
|
* @see {@link modifySome} for optionally updating while returning a separate result
|
|
*
|
|
* @category setters
|
|
* @since 2.0.0
|
|
*/
|
|
export const modify = dual<
|
|
<A, B>(f: (a: A) => readonly [B, A]) => (self: Ref<A>) => Effect.Effect<B>,
|
|
<A, B>(self: Ref<A>, f: (a: A) => readonly [B, A]) => Effect.Effect<B>
|
|
>(2, (self, f) =>
|
|
Effect.sync(() => {
|
|
const [b, a] = f(self.ref.current)
|
|
self.ref.current = a
|
|
return b
|
|
}))
|
|
|
|
/**
|
|
* Computes a result atomically and optionally updates the value of the `Ref`.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to compute a return value while optionally updating a plain `Ref`.
|
|
*
|
|
* **Details**
|
|
*
|
|
* The callback receives the current value and returns `[result, nextValue]`,
|
|
* where `nextValue` is an `Option`. If `nextValue` is `Option.some(value)`,
|
|
* the `Ref` is updated to `value`; if it is `Option.none()`, the `Ref` is left
|
|
* unchanged. The returned effect always succeeds with `result`.
|
|
*
|
|
* **Example** (Conditionally modifying a value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Option, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(5)
|
|
*
|
|
* // Only modify if value is greater than 3
|
|
* const result1 = yield* Ref.modifySome(
|
|
* counter,
|
|
* (n) =>
|
|
* n > 3
|
|
* ? [`incremented ${n}`, Option.some(n + 10)]
|
|
* : ["no change", Option.none()]
|
|
* )
|
|
*
|
|
* console.log(result1) // "incremented 5"
|
|
*
|
|
* const current1 = yield* Ref.get(counter)
|
|
* console.log(current1) // 15
|
|
*
|
|
* // Try to modify with a condition that fails
|
|
* const result2 = yield* Ref.modifySome(
|
|
* counter,
|
|
* (n) =>
|
|
* n < 10
|
|
* ? [`decremented ${n}`, Option.some(n - 5)]
|
|
* : ["no change", Option.none()]
|
|
* )
|
|
*
|
|
* console.log(result2) // "no change"
|
|
*
|
|
* const current2 = yield* Ref.get(counter)
|
|
* console.log(current2) // 15 (unchanged)
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link modify} for always storing a new value
|
|
* @see {@link updateSome} for optional updates without a separate return value
|
|
*
|
|
* @category setters
|
|
* @since 2.0.0
|
|
*/
|
|
export const modifySome: {
|
|
<B, A>(pf: (a: A) => readonly [B, Option.Option<A>]): (self: Ref<A>) => Effect.Effect<B>
|
|
<A, B>(self: Ref<A>, pf: (a: A) => readonly [B, Option.Option<A>]): Effect.Effect<B>
|
|
} = dual<
|
|
<B, A>(
|
|
pf: (a: A) => readonly [B, Option.Option<A>]
|
|
) => (self: Ref<A>) => Effect.Effect<B>,
|
|
<A, B>(
|
|
self: Ref<A>,
|
|
pf: (a: A) => readonly [B, Option.Option<A>]
|
|
) => Effect.Effect<B>
|
|
>(2, (self, pf) =>
|
|
modify(self, (value) => {
|
|
const [b, option] = pf(value)
|
|
return [b, option._tag === "None" ? value : option.value]
|
|
}))
|
|
|
|
/**
|
|
* Updates the value of the Ref atomically using the given function.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to apply a `Ref` state transition without returning a value.
|
|
*
|
|
* **Example** (Updating a value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(5)
|
|
*
|
|
* // Update the value
|
|
* yield* Ref.update(counter, (n) => n * 2)
|
|
*
|
|
* const value = yield* Ref.get(counter)
|
|
* console.log(value) // 10
|
|
* })
|
|
*
|
|
* // Using multiple operations
|
|
* const program2 = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(5)
|
|
* yield* Ref.update(counter, (n: number) => n + 10)
|
|
* const value = yield* Ref.get(counter)
|
|
* console.log(value) // 15
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link updateAndGet} for returning the new value
|
|
* @see {@link getAndUpdate} for returning the previous value
|
|
*
|
|
* @category setters
|
|
* @since 2.0.0
|
|
*/
|
|
export const update = dual<
|
|
<A>(f: (a: A) => A) => (self: Ref<A>) => Effect.Effect<void>,
|
|
<A>(self: Ref<A>, f: (a: A) => A) => Effect.Effect<void>
|
|
>(2, <A>(self: Ref<A>, f: (a: A) => A) =>
|
|
Effect.sync(() => {
|
|
self.ref.current = f(self.ref.current)
|
|
}))
|
|
|
|
/**
|
|
* Updates the value of the Ref atomically using the given function and returns the new value.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to apply a `Ref` state transition and return the new stored value.
|
|
*
|
|
* **Example** (Updating and returning the new value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(5)
|
|
*
|
|
* // Update and get the new value in one operation
|
|
* const newValue = yield* Ref.updateAndGet(counter, (n) => n * 3)
|
|
* console.log(newValue) // 15
|
|
*
|
|
* // Verify the ref contains the new value
|
|
* const current = yield* Ref.get(counter)
|
|
* console.log(current) // 15
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link update} for updating without returning the new value
|
|
* @see {@link getAndUpdate} for returning the previous value instead
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const updateAndGet = dual<
|
|
<A>(f: (a: A) => A) => (self: Ref<A>) => Effect.Effect<A>,
|
|
<A>(self: Ref<A>, f: (a: A) => A) => Effect.Effect<A>
|
|
>(2, <A>(self: Ref<A>, f: (a: A) => A) => Effect.sync(() => self.ref.current = f(self.ref.current)))
|
|
|
|
/**
|
|
* Updates the value of the Ref atomically using the given partial function.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to apply a conditional `Ref` update without returning a value.
|
|
*
|
|
* **Details**
|
|
*
|
|
* If the partial function returns `Option.some`, the Ref is updated with the
|
|
* new value. If it returns `Option.none`, the Ref is left unchanged.
|
|
*
|
|
* **Example** (Conditionally updating a value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Option, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(5)
|
|
*
|
|
* // Only update if value is even
|
|
* yield* Ref.updateSome(
|
|
* counter,
|
|
* (n) => n % 2 === 0 ? Option.some(n * 2) : Option.none()
|
|
* )
|
|
*
|
|
* let current = yield* Ref.get(counter)
|
|
* console.log(current) // 5 (unchanged because 5 is odd)
|
|
*
|
|
* // Set to even number and try again
|
|
* yield* Ref.set(counter, 6)
|
|
* yield* Ref.updateSome(
|
|
* counter,
|
|
* (n) => n % 2 === 0 ? Option.some(n * 2) : Option.none()
|
|
* )
|
|
*
|
|
* current = yield* Ref.get(counter)
|
|
* console.log(current) // 12 (updated because 6 is even)
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link update} for always applying an update
|
|
* @see {@link updateSomeAndGet} for returning the resulting current value
|
|
*
|
|
* @category setters
|
|
* @since 2.0.0
|
|
*/
|
|
export const updateSome = dual<
|
|
<A>(f: (a: A) => Option.Option<A>) => (self: Ref<A>) => Effect.Effect<void>,
|
|
<A>(self: Ref<A>, f: (a: A) => Option.Option<A>) => Effect.Effect<void>
|
|
>(2, <A>(self: Ref<A>, f: (a: A) => Option.Option<A>) =>
|
|
Effect.sync(() => {
|
|
const option = f(self.ref.current)
|
|
if (option._tag === "Some") {
|
|
self.ref.current = option.value
|
|
}
|
|
}))
|
|
|
|
/**
|
|
* Updates the value of the Ref atomically using the given partial function and returns the current value.
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use to apply a conditional `Ref` update and return the resulting current
|
|
* value.
|
|
*
|
|
* **Details**
|
|
*
|
|
* If the partial function returns `Option.some`, the Ref is updated with the
|
|
* new value. If it returns `Option.none`, the Ref is left unchanged. The effect
|
|
* returns the current value of the Ref after the potential update.
|
|
*
|
|
* **Example** (Conditionally updating and returning the current value)
|
|
*
|
|
* ```ts
|
|
* import { Effect, Option, Ref } from "effect"
|
|
*
|
|
* const program = Effect.gen(function*() {
|
|
* const counter = yield* Ref.make(10)
|
|
*
|
|
* // Only update if value is greater than 5
|
|
* const result1 = yield* Ref.updateSomeAndGet(
|
|
* counter,
|
|
* (n) => n > 5 ? Option.some(n / 2) : Option.none()
|
|
* )
|
|
* console.log(result1) // 5 (updated and returned)
|
|
*
|
|
* // Try to update again with same condition
|
|
* const result2 = yield* Ref.updateSomeAndGet(
|
|
* counter,
|
|
* (n) => n > 5 ? Option.some(n / 2) : Option.none()
|
|
* )
|
|
* console.log(result2) // 5 (unchanged because 5 is not > 5)
|
|
* })
|
|
* ```
|
|
*
|
|
* @see {@link updateSome} for conditional updates without returning a value
|
|
* @see {@link updateAndGet} for always updating and returning the new value
|
|
*
|
|
* @category mutations
|
|
* @since 2.0.0
|
|
*/
|
|
export const updateSomeAndGet = dual<
|
|
<A>(pf: (a: A) => Option.Option<A>) => (self: Ref<A>) => Effect.Effect<A>,
|
|
<A>(self: Ref<A>, pf: (a: A) => Option.Option<A>) => Effect.Effect<A>
|
|
>(2, <A>(self: Ref<A>, pf: (a: A) => Option.Option<A>) =>
|
|
Effect.sync(() => {
|
|
const option = pf(self.ref.current)
|
|
if (option._tag === "Some") {
|
|
self.ref.current = option.value
|
|
}
|
|
return self.ref.current
|
|
}))
|
|
|
|
/**
|
|
* Gets the current value of the Ref synchronously (unsafe version).
|
|
*
|
|
* **When to use**
|
|
*
|
|
* Use when you need immediate synchronous access and can guarantee that
|
|
* reading the `Ref` outside of `Effect` is safe.
|
|
*
|
|
* **Gotchas**
|
|
*
|
|
* Prefer `Ref.get` for Effect-wrapped access in Effect programs.
|
|
*
|
|
* **Example** (Reading a ref unsafely)
|
|
*
|
|
* ```ts
|
|
* import { Ref } from "effect"
|
|
*
|
|
* // Create a ref directly
|
|
* const counter = Ref.makeUnsafe(42)
|
|
*
|
|
* // Get the value synchronously
|
|
* const value = Ref.getUnsafe(counter)
|
|
* console.log(value) // 42
|
|
*
|
|
* // Note: This is unsafe and should be used carefully
|
|
* // Prefer Ref.get for Effect-wrapped access
|
|
* ```
|
|
*
|
|
* @category getters
|
|
* @since 4.0.0
|
|
*/
|
|
export const getUnsafe = <A>(self: Ref<A>): A => self.ref.current
|