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

kyo.kernel.ArrowEffect.scala Maven / Gradle / Ivy

The newest version!
package kyo.kernel

import internal.*
import kyo.Const
import kyo.Flat
import kyo.Frame
import kyo.Tag
import scala.annotation.nowarn
import scala.util.control.NonFatal

/** Represents abstract functions whose implementations are provided later by a handler.
  *
  * ArrowEffect captures the shape of a function without specifying its implementation. It describes a transformation from Input[A] to
  * Output[A] for any type A, but defers how that transformation actually happens until a handler interprets it. This makes it a powerful
  * way to write code that is abstract over how its operations are performed.
  *
  * ArrowEffect supports multi-shot continuations, meaning that handlers can invoke the continuation function multiple times or not at all.
  * This enables powerful control flow effects like backtracking, non-determinism, or early returns. For example, a choice effect could
  * invoke its continuation multiple times with different values to explore multiple execution paths.
  *
  * The type parameters Input[_] and Output[_] define the "shape" of the function being abstracted:
  *
  * @tparam Input
  *   The input type constructor - what arguments the function takes
  * @tparam Output
  *   The output type constructor - what results the function produces
  *
  * Every use of an ArrowEffect creates a suspended function call. This suspended call contains all the information needed to perform the
  * operation, but doesn't specify how to perform it.
  *
  * A handler then provides the actual function implementation that determines what happens when that suspended call is executed. Each
  * handler takes two parameters: an input value of type I[C] that contains the input of the operation, and a continuation function
  * representing the remainder of the computation from the point where the effect was suspended to the point where it's being handled.
  *
  * When defining concrete effects, ArrowEffect is commonly used with two special type constructors: Const and Id. The Const[X] type
  * constructor ignores its type parameter and always returns X, while Id[X] simply returns X unchanged. For instance, an effect that needs
  * to fail with errors of type E would use Const[E] as its input type - it only needs the error value itself, not any type parameters.
  * Similarly, an effect for making choices among values would use Id as its output type - it passes through the chosen value unchanged.
  */
abstract class ArrowEffect[-Input[_], +Output[_]] extends Effect

object ArrowEffect:

    final class SuspendOps[A](dummy: Unit) extends AnyVal:

        /** Creates a suspended computation that requests a function implementation from an arrow effect. This establishes a requirement for
          * a function that must be satisfied by a handler higher up in the program. The requirement becomes part of the effect type,
          * ensuring that handlers must provide the requested function before the program can execute.
          *
          * @param effectTag
          *   Identifies which arrow effect to request the function from
          * @param funcionInput
          *   The input value to be transformed by the function
          * @return
          *   A computation that will receive the requested function when executed
          */
        @nowarn("msg=anonymous")
        inline def apply[I[_], O[_], E <: ArrowEffect[I, O]](
            inline effectTag: Tag[E],
            inline funcionInput: I[A]
        )(using inline _frame: Frame): O[A] < E =
            new KyoSuspend[I, O, E, A, O[A], E]:
                def frame = _frame
                def tag   = effectTag
                def input = funcionInput
                def apply(v: O[A], context: Context)(using Safepoint) =
                    v
    end SuspendOps

    /** See [[SuspendOps.apply]] */
    inline def suspend[A]: SuspendOps[A] = SuspendOps(())

    @nowarn("msg=anonymous")
    final class SuspendAndMapOps[A](dummy: Unit) extends AnyVal:

        /** Creates a suspended computation that requests a function implementation and transforms its result immediately upon receipt. This
          * combines the operations of requesting and transforming a function into a single step.
          *
          * @param effectTag
          *   Identifies which arrow effect to request the function from
          * @param funcionInput
          *   The input value to be transformed by the function
          * @param f
          *   The function to transform the handler's result
          * @return
          *   A computation containing the transformed result
          */
        inline def apply[I[_], O[_], E <: ArrowEffect[I, O], B, S](
            inline effectTag: Tag[E],
            inline funcionInput: I[A]
        )(
            inline f: Safepoint ?=> O[A] => B < S
        )(using inline _frame: Frame): B < (S & E) =
            new KyoSuspend[I, O, E, A, B, S & E]:
                def frame = _frame
                def tag   = effectTag
                def input = funcionInput
                def apply(v: O[A], context: Context)(using Safepoint) =
                    Safepoint.handle(v)(
                        suspend = f(v),
                        continue = f(v)
                    )
    end SuspendAndMapOps

    /** See [[SuspendAndMapOps.apply]] */
    inline def suspendAndMap[A]: SuspendAndMapOps[A] = SuspendAndMapOps(())

    object handle:

        /** Handles an arrow effect by providing a handler function implementation.
          *
          * @param effectTag
          *   Identifies which arrow effect to handle
          * @param v
          *   The computation requiring the function implementation
          * @param handle
          *   The function implementation to provide
          * @return
          *   The computation result with the function implementation provided
          */
        inline def apply[I[_], O[_], E <: ArrowEffect[I, O], A, S, S2](
            inline effectTag: Tag[E],
            v: A < (E & S)
        )(
            inline handle: [C] => (I[C], Safepoint ?=> O[C] => A < (E & S & S2)) => A < (E & S & S2)
        )(
            using
            inline _frame: Frame,
            inline flat: Flat[A],
            safepoint: Safepoint
        ): A < (S & S2) =
            @nowarn("msg=anonymous")
            def handleLoop(v: A < (E & S & S2), context: Context)(using Safepoint): A < (S & S2) =
                v match
                    case kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked if effectTag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle[Any](kyo.input, kyo(_, context)),
                            continue = handleLoop(_, context),
                            suspend = handleLoop(kyo, context)
                        )
                    case kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2] @unchecked =>
                        new KyoContinue[IX, OX, EX, Any, A, S & S2](kyo):
                            def frame = _frame
                            def apply(v: OX[Any], context: Context)(using Safepoint) =
                                handleLoop(kyo(v, context), context)
                        end new
                    case kyo =>
                        kyo.asInstanceOf[A]
                end match
            end handleLoop
            handleLoop(v, Context.empty)
        end apply

        /** Handles two arrow effects by providing function implementations.
          *
          * @param effect1Tag
          *   First effect tag to handle
          * @param effect2Tag
          *   Second effect tag to handle
          * @param v
          *   The computation requiring the function implementations
          * @param handle1
          *   Implementation for first effect
          * @param handle2
          *   Implementation for second effect
          * @return
          *   The computation result with both function implementations provided
          */
        inline def apply[I1[_], O1[_], E1 <: ArrowEffect[I1, O1], I2[_], O2[_], E2 <: ArrowEffect[I2, O2], A, S, S2](
            inline effect1Tag: Tag[E1],
            inline effect2Tag: Tag[E2],
            v: A < (E1 & E2 & S)
        )(
            inline handle1: [C] => (I1[C], Safepoint ?=> O1[C] => A < (E1 & E2 & S & S2)) => A < (E1 & E2 & S & S2),
            inline handle2: [C] => (I2[C], Safepoint ?=> O2[C] => A < (E1 & E2 & S & S2)) => A < (E1 & E2 & S & S2)
        )(
            using
            inline _frame: Frame,
            inline flat: Flat[A],
            safepoint: Safepoint
        ): A < (S & S2) =
            @nowarn("msg=anonymous")
            def handle2Loop(kyo: A < (E1 & E2 & S & S2), context: Context)(using Safepoint): A < (S & S2) =
                kyo match
                    case kyo: KyoSuspend[I1, O1, E1, Any, A, E1 & E2 & S & S2] @unchecked if effect1Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle1[Any](kyo.input, kyo(_, context)),
                            suspend = handle2Loop(kyo, context),
                            continue = handle2Loop(_, context)
                        )
                    case kyo: KyoSuspend[I2, O2, E2, Any, A, E1 & E2 & S & S2] @unchecked if effect2Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle2[Any](kyo.input, kyo(_, context)),
                            suspend = handle2Loop(kyo, context),
                            continue = handle2Loop(_, context)
                        )
                    case kyo: KyoSuspend[IX, OX, EX, Any, A, E1 & E2 & S & S2] @unchecked =>
                        new KyoContinue[IX, OX, EX, Any, A, S & S2](kyo):
                            def frame = _frame
                            def apply(v: OX[Any], context: Context)(using Safepoint) =
                                handle2Loop(kyo(v, context), context)
                        end new
                    case kyo =>
                        kyo.asInstanceOf[A]
                end match
            end handle2Loop
            handle2Loop(v, Context.empty)
        end apply

        /** Handles three arrow effects by providing function implementations.
          *
          * @param effect1Tag
          *   First effect tag to handle
          * @param effect2Tag
          *   Second effect tag to handle
          * @param effect3Tag
          *   Third effect tag to handle
          * @param v
          *   The computation requiring the function implementations
          * @param handle1
          *   Implementation for first effect
          * @param handle2
          *   Implementation for second effect
          * @param handle3
          *   Implementation for third effect
          * @return
          *   The computation result with all three function implementations provided
          */
        inline def apply[I1[_], O1[_], E1 <: ArrowEffect[I1, O1], I2[_], O2[_], E2 <: ArrowEffect[I2, O2], I3[_], O3[_], E3 <: ArrowEffect[
            I3,
            O3
        ], A, S, S2](
            inline effect1Tag: Tag[E1],
            inline effect2Tag: Tag[E2],
            inline effect3Tag: Tag[E3],
            v: A < (E1 & E2 & E3 & S)
        )(
            inline handle1: [C] => (I1[C], Safepoint ?=> O1[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2),
            inline handle2: [C] => (I2[C], Safepoint ?=> O2[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2),
            inline handle3: [C] => (I3[C], Safepoint ?=> O3[C] => A < (E1 & E2 & E3 & S & S2)) => A < (E1 & E2 & E3 & S & S2)
        )(
            using
            inline _frame: Frame,
            inline flat: Flat[A],
            safepoint: Safepoint
        ): A < (S & S2) =
            @nowarn("msg=anonymous")
            def handle3Loop(v: A < (E1 & E2 & E3 & S & S2), context: Context)(using Safepoint): A < (S & S2) =
                v match
                    case kyo: KyoSuspend[I1, O1, E1, Any, A, E1 & E2 & E3 & S & S2] @unchecked if effect1Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle1[Any](kyo.input, kyo(_, context)),
                            suspend = handle3Loop(kyo, context),
                            continue = handle3Loop(_, context)
                        )
                    case kyo: KyoSuspend[I2, O2, E2, Any, A, E1 & E2 & E3 & S & S2] @unchecked if effect2Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle2[Any](kyo.input, kyo(_, context)),
                            suspend = handle3Loop(kyo, context),
                            continue = handle3Loop(_, context)
                        )
                    case kyo: KyoSuspend[I3, O3, E3, Any, A, E1 & E2 & E3 & S & S2] @unchecked if effect3Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle3[Any](kyo.input, kyo(_, context)),
                            suspend = handle3Loop(kyo, context),
                            continue = handle3Loop(_, context)
                        )
                    case kyo: KyoSuspend[IX, OX, EX, Any, A, E1 & E2 & E3 & S & S2] @unchecked =>
                        new KyoContinue[IX, OX, EX, Any, A, S & S2](kyo):
                            def frame = _frame
                            def apply(v: OX[Any], context: Context)(using Safepoint) =
                                handle3Loop(kyo(v, context), context)
                        end new
                    case kyo =>
                        kyo.asInstanceOf[A]
                end match
            end handle3Loop
            handle3Loop(v, Context.empty)
        end apply

        /** Handles four arrow effects by providing function implementations.
          *
          * @param effect1Tag
          *   First effect tag to handle
          * @param effect2Tag
          *   Second effect tag to handle
          * @param effect3Tag
          *   Third effect tag to handle
          * @param effect4Tag
          *   Fourth effect tag to handle
          * @param v
          *   The computation requiring the function implementations
          * @param handle1
          *   Implementation for first effect
          * @param handle2
          *   Implementation for second effect
          * @param handle3
          *   Implementation for third effect
          * @param handle4
          *   Implementation for fourth effect
          * @return
          *   The computation result with all four function implementations provided
          */
        inline def apply[I1[_], O1[_], E1 <: ArrowEffect[I1, O1], I2[_], O2[_], E2 <: ArrowEffect[I2, O2], I3[_], O3[_], E3 <: ArrowEffect[
            I3,
            O3
        ], I4[_], O4[_], E4 <: ArrowEffect[I4, O4], A, S, S2](
            inline effect1Tag: Tag[E1],
            inline effect2Tag: Tag[E2],
            inline effect3Tag: Tag[E3],
            inline effect4Tag: Tag[E4],
            v: A < (E1 & E2 & E3 & E4 & S)
        )(
            inline handle1: [C] => (I1[C], Safepoint ?=> O1[C] => A < (E1 & E2 & E3 & E4 & S & S2)) => A < (E1 & E2 & E3 & E4 & S & S2),
            inline handle2: [C] => (I2[C], Safepoint ?=> O2[C] => A < (E1 & E2 & E3 & E4 & S & S2)) => A < (E1 & E2 & E3 & E4 & S & S2),
            inline handle3: [C] => (I3[C], Safepoint ?=> O3[C] => A < (E1 & E2 & E3 & E4 & S & S2)) => A < (E1 & E2 & E3 & E4 & S & S2),
            inline handle4: [C] => (I4[C], Safepoint ?=> O4[C] => A < (E1 & E2 & E3 & E4 & S & S2)) => A < (E1 & E2 & E3 & E4 & S & S2)
        )(
            using
            inline _frame: Frame,
            inline flat: Flat[A],
            safepoint: Safepoint
        ): A < (S & S2) =
            @nowarn("msg=anonymous")
            def handle4Loop(v: A < (E1 & E2 & E3 & E4 & S & S2), context: Context)(using Safepoint): A < (S & S2) =
                v match
                    case kyo: KyoSuspend[I1, O1, E1, Any, A, E1 & E2 & E3 & E4 & S & S2] @unchecked if effect1Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle1[Any](kyo.input, kyo(_, context)),
                            suspend = handle4Loop(kyo, context),
                            continue = handle4Loop(_, context)
                        )
                    case kyo: KyoSuspend[I2, O2, E2, Any, A, E1 & E2 & E3 & E4 & S & S2] @unchecked if effect2Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle2[Any](kyo.input, kyo(_, context)),
                            suspend = handle4Loop(kyo, context),
                            continue = handle4Loop(_, context)
                        )
                    case kyo: KyoSuspend[I3, O3, E3, Any, A, E1 & E2 & E3 & E4 & S & S2] @unchecked if effect3Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle3[Any](kyo.input, kyo(_, context)),
                            suspend = handle4Loop(kyo, context),
                            continue = handle4Loop(_, context)
                        )
                    case kyo: KyoSuspend[I4, O4, E4, Any, A, E1 & E2 & E3 & E4 & S & S2] @unchecked if effect4Tag =:= kyo.tag =>
                        Safepoint.handle(kyo.input)(
                            eval = handle4[Any](kyo.input, kyo(_, context)),
                            suspend = handle4Loop(kyo, context),
                            continue = handle4Loop(_, context)
                        )
                    case kyo: KyoSuspend[IX, OX, EX, Any, A, E1 & E2 & E3 & E4 & S & S2] @unchecked =>
                        new KyoContinue[IX, OX, EX, Any, A, S & S2](kyo):
                            def frame = _frame
                            def apply(v: OX[Any], context: Context)(using Safepoint) =
                                handle4Loop(kyo(v, context), context)
                        end new
                    case kyo =>
                        kyo.asInstanceOf[A]
                end match
            end handle4Loop
            handle4Loop(v, Context.empty)
        end apply
    end handle

    /** Handles the first occurrence of an arrow effect and transforms the final result. This is useful when you want to handle just the
      * first instance of an effect and transform its result into a different type, while leaving any subsequent occurrences of the effect
      * unhandled.
      *
      * @param effectTag
      *   Identifies which arrow effect to handle
      * @param v
      *   The computation containing the effect to handle
      * @param handle
      *   Function to handle the first occurrence of the effect and transform its result
      * @param done
      *   Function to transform the final result if no effect is found
      * @return
      *   The transformed computation result
      */
    inline def handleFirst[I[_], O[_], E <: ArrowEffect[I, O], A, B, S, S2](effectTag: Tag[E], v: A < (E & S))(
        inline handle: [C] => (I[C], O[C] => A < (E & S)) => B < S2,
        inline done: A => B < S2
    )(
        using
        inline _frame: Frame,
        inline flat: Flat[A],
        safepoint: Safepoint
    ): B < (S & S2) =
        @nowarn("msg=anonymous")
        def handleFirstLoop(v: A < (E & S), context: Context)(using Safepoint): B < (S & S2) =
            v match
                case kyo: KyoSuspend[I, O, E, Any, A, E & S] @unchecked if effectTag =:= kyo.tag =>
                    Safepoint.handle(kyo.input)(
                        suspend = handleFirstLoop(kyo, context),
                        continue = handle[Any](kyo.input, kyo(_, context))
                    )
                case kyo: KyoSuspend[IX, OX, EX, Any, A, E & S] @unchecked =>
                    new KyoContinue[IX, OX, EX, Any, B, S & S2](kyo):
                        def frame = _frame
                        def apply(v: OX[Any], context: Context)(using Safepoint) =
                            handleFirstLoop(kyo(v, context), context)
                    end new
                case kyo =>
                    done(kyo.asInstanceOf[A])
            end match
        end handleFirstLoop
        handleFirstLoop(v, Context.empty)
    end handleFirst

    /** Handles an arrow effect with a stateful implementation. This combines function implementation with state management, allowing the
      * handler to maintain and modify state as it processes operations. The state is threaded through the computation and can influence how
      * operations are processed.
      *
      * @param effectTag
      *   Identifies which arrow effect to handle
      * @param state
      *   The initial state value
      * @param v
      *   The computation requiring the function implementation
      * @param handle
      *   The stateful implementation to provide
      * @return
      *   The computation result with the stateful implementation provided
      */
    inline def handleState[I[_], O[_], E <: ArrowEffect[I, O], State, A, B, S, S2, S3](
        inline effectTag: Tag[E],
        inline state: State,
        v: A < (E & S)
    )(
        inline handle: [C] => (I[C], State, Safepoint ?=> O[C] => A < (E & S & S2)) => (State, A < (E & S & S2)) < S3,
        inline done: (State, A) => B < (S & S2 & S3) = (_: State, v: A) => v
    )(
        using
        inline _frame: Frame,
        inline flat: Flat[A],
        safepoint: Safepoint
    ): B < (S & S2 & S3) =
        @nowarn("msg=anonymous")
        def handleLoop(state: State, v: A < (E & S & S2 & S3), context: Context)(using Safepoint): B < (S & S2 & S3) =
            v match
                case kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked if effectTag =:= kyo.tag =>
                    handle(kyo.input, state, kyo(_, context)).map(handleLoop(_, _, context))
                case kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2] @unchecked =>
                    new KyoContinue[IX, OX, EX, Any, B, S & S2 & S3](kyo):
                        def frame = _frame
                        def apply(v: OX[Any], context: Context)(using Safepoint) =
                            handleLoop(state, kyo(v, context), context)
                    end new
                case kyo =>
                    done(state, kyo.asInstanceOf[A])
            end match
        end handleLoop
        handleLoop(state, v, Context.empty)
    end handleState

    private[kyo] inline def handleCatching[I[_], O[_], E <: ArrowEffect[I, O], A, B, S, S2, S3](
        inline effectTag: Tag[E],
        inline v: => A < (E & S)
    )(
        inline handle: [C] => (I[C], Safepoint ?=> O[C] => A < (E & S & S2)) => A < (E & S & S2),
        inline done: A => B < S3 = (v: A) => v,
        inline accept: [C] => I[C] => Boolean = [C] => (_: I[C]) => true,
        inline recover: Throwable => B < (S & S2 & S3)
    )(
        using
        inline _frame: Frame,
        inline flat: Flat[A],
        safepoint: Safepoint
    ): B < (S & S2 & S3) =
        @nowarn("msg=anonymous")
        def handleLoop(v: A < (E & S & S2 & S3), context: Context)(using Safepoint): B < (S & S2 & S3) =
            v match
                case kyo: KyoSuspend[I, O, E, Any, A, E & S & S2] @unchecked if effectTag =:= kyo.tag && accept(kyo.input) =>
                    Safepoint.handle(kyo.input)(
                        eval = handle[Any](kyo.input, kyo(_, context)),
                        continue = handleLoop(_, context),
                        suspend = handleLoop(kyo, context)
                    )
                case kyo: KyoSuspend[IX, OX, EX, Any, A, E & S & S2 & S3] @unchecked =>
                    new KyoContinue[IX, OX, EX, Any, B, S & S2 & S3](kyo):
                        def frame = _frame
                        def apply(v: OX[Any], context: Context)(using Safepoint) =
                            try handleLoop(kyo(v, context), context)
                            catch
                                case ex if NonFatal(ex) =>
                                    Safepoint.enrich(ex)
                                    recover(ex)
                        end apply
                    end new
                case kyo =>
                    done(kyo.asInstanceOf[A])
            end match
        end handleLoop

        try handleLoop(v, Context.empty)
        catch
            case ex if NonFatal(ex) =>
                Safepoint.enrich(ex)
                recover(ex)
        end try
    end handleCatching

    private[kyo] inline def handlePartial[I1[_], O1[_], E1 <: ArrowEffect[I1, O1], I2[_], O2[_], E2 <: ArrowEffect[I2, O2], A, S, S2](
        inline tag1: Tag[E1],
        inline tag2: Tag[E2],
        v: A < (E1 & E2 & S),
        context: Context
    )(
        inline stop: => Boolean,
        inline handle1: [C] => (I1[C], Safepoint ?=> O1[C] => A < (E1 & E2 & S & S2)) => A < (E1 & E2 & S & S2),
        inline handle2: [C] => (I2[C], Safepoint ?=> O2[C] => A < (E1 & E2 & S & S2)) => A < (E1 & E2 & S & S2)
    )(
        using
        inline _frame: Frame,
        inline flat: Flat[A],
        safepoint: Safepoint
    ): A < (E1 & E2 & S & S2) =
        def partialLoop(v: A < (E1 & E2 & S & S2), context: Context)(using safepoint: Safepoint): A < (E1 & E2 & S & S2) =
            if stop then v
            else
                v match
                    case kyo: KyoSuspend[?, ?, ?, ?, ?, ?] =>
                        type Suspend[I[_], O[_], E <: ArrowEffect[I, O]] = KyoSuspend[I, O, E, Any, A, E1 & E2 & S & S2]
                        if kyo.tag =:= Tag[Defer] then
                            val k = kyo.asInstanceOf[Suspend[Const[Unit], Const[Unit], Defer]]
                            partialLoop(k((), context), context)
                        else
                            safepoint.pushFrame(kyo.frame)
                            if tag1 =:= kyo.tag then
                                val k = kyo.asInstanceOf[Suspend[I1, O1, E1]]
                                partialLoop(handle1[Any](k.input, k(_, context)), context)
                            else if tag2 =:= kyo.tag then
                                val k = kyo.asInstanceOf[Suspend[I2, O2, E2]]
                                partialLoop(handle2[Any](k.input, k(_, context)), context)
                            else
                                v
                            end if
                        end if
                    case _ =>
                        v
                end match
        end partialLoop
        partialLoop(v, context)
    end handlePartial
end ArrowEffect




© 2015 - 2025 Weber Informatics LLC | Privacy Policy