All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kyo.Isolate.scala Maven / Gradle / Ivy

The newest version!
package kyo

import internal.*
import kyo.Flat
import kyo.Frame
import kyo.Kyo
import kyo.Var.isolate
import kyo.kernel.ContextEffect
import kyo.kernel.Safepoint.Interceptor
import scala.quoted.*

/** Provides fine-grained control over state isolation and propagation within computations.
  *
  * The `Isolate` mechanism allows you to control how effect state is handled, contained, and propagated during computation. This is
  * essential for implementing effects that need careful control over their state visibility and ordering of operations.
  *
  * While Isolate provides a low-level API through `use`, `resume`, and `restore` for advanced use cases, most users should rely on the
  * high-level `run` method which composes these operations safely and handles all the complexity of state management internally.
  *
  * Effects typically provide their isolation strategies through their companion objects (e.g. `Var.isolate.update`, `Check.isolate.merge`).
  * These isolates can be composed using `andThen`, which enforces the ordering of effect handling - ensuring effects are handled in the
  * specified sequence.
  *
  * Key use cases include:
  *   - Transactional effects where state changes may need to be contained until commit
  *   - Effects that require explicit control over state propagation order
  *   - Parallel computations where state needs to be merged in specific ways
  *   - Scenarios where effect state should be temporarily scoped or discarded
  *   - Error handling where partial effects must be rolled back atomically
  *   - Complex workflows requiring nested transaction boundaries
  *
  * Implementations typically choose one of three strategies:
  *   - Merge: Combine states when multiple computations need to reconcile their effects
  *   - Update: Unconditionally updates the state with the last available value
  *   - Discard: Prevent state from propagating beyond a defined scope
  *
  * A key feature of Isolate is its interaction with error handling through the Abort effect. When an Abort occurs within an isolated scope,
  * the isolation acts as a transaction boundary - isolated effects within the isolated scope are rolled back or discarded, maintaining
  * consistency of the outer scope. This transactional behavior applies across all effect types:
  *   - State changes are rolled back to their pre-isolation values
  *   - Emitted values are discarded rather than being merged
  *   - Cached computations remain isolated and don't propagate
  *   - Nested isolations maintain these guarantees at each level
  *
  * This combination of isolation strategies and error handling makes Isolate particularly powerful for implementing robust,
  * transaction-like behaviors across different effect types while maintaining clean separation of concerns.
  *
  * @tparam S
  *   The type of effect being isolated
  */
abstract class Isolate[S]:
    self =>

    /** The type of state managed by this isolate.
      *
      * This abstract type member represents the concrete state type that will be managed during isolation. The exact type depends on the
      * effect being isolated.
      */
    type State

    /** Isolates a computation.
      *
      * This is the primary method users should use for isolation. It handles all the complexity of state management internally by composing
      * the lower-level isolation operations.
      *
      * @param v
      *   The computation to run with isolation
      * @return
      *   The computation result with isolated state handling
      */
    def run[A: Flat, S2](v: A < S2)(using Frame): A < (S & S2) =
        use(resume(_, v).map(restore(_, _)))

    /** Starts the isolation flow by providing access to the initial state.
      *
      * Advanced usage: This is the first step in the low-level state isolation API. Most users should use `run` instead. It provides access
      * to the current effect state, which can then be used with resume to begin isolated execution.
      *
      * @param f
      *   Function that receives initial state and starts the isolation flow
      * @return
      *   A computation that includes the isolated effect
      */
    def use[A, S2](f: State => A < S2)(using Frame): A < (S & S2)

    /** Begins isolated execution of a computation using the provided state.
      *
      * Advanced usage: After obtaining initial state via use, resume begins isolated execution of a computation. Most users should use
      * `run` instead. It returns both the final state and computation result, allowing the state to be later restored via restore.
      *
      * @param state
      *   Initial state obtained from use
      * @param v
      *   Computation to execute in isolation
      * @return
      *   A pair of final state and computation result
      */
    def resume[A: Flat, S2](state: State, v: A < (S & S2))(using Frame): (State, A) < S2

    /** Completes the isolation flow by restoring the final state.
      *
      * Advanced usage: This is the final step in the low-level isolation API. Most users should use `run` instead. Takes the state and
      * result from resume and determines how that state should be applied when returning to the normal execution context. The exact
      * behavior (merge/update/discard) depends on the isolation strategy.
      *
      * @param state
      *   Final state from resume to restore
      * @param v
      *   Computation to continue with restored state
      * @return
      *   Computation result with restored state
      */
    def restore[A: Flat, S2](state: State, v: A < S2)(using Frame): A < (S & S2)

    /** Combines this isolate with another one in sequence.
      *
      * Creates a new isolate that manages both effects' states, defining how their respective states interact and propagate when composed
      * together.
      *
      * @param next
      *   The isolate to combine with this one
      * @return
      *   A new isolate managing both effects
      */
    def andThen[S2](next: Isolate[S2])(using Frame): Isolate[S & S2] =
        new Isolate[S & S2]:
            type State = (self.State, next.State)

            def use[A, S2](f: State => A < S2)(using Frame) =
                self.use(s1 => next.use(s2 => f((s1, s2))))

            def resume[A: Flat, S3](state: (self.State, next.State), v: A < (S & S2 & S3))(using Frame) =
                self.resume(state._1, next.resume(state._2, v)).map {
                    case (s1, (s2, r)) => ((s1, s2), r)
                }
            def restore[A: Flat, S2](state: (self.State, next.State), v: A < S2)(using Frame) =
                self.restore(state._1, next.restore(state._2, v))
        end new
    end andThen

end Isolate




© 2015 - 2025 Weber Informatics LLC | Privacy Policy