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

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

The newest version!
package kyo.kernel

import internal.*
import kyo.*
import kyo.Maybe
import scala.annotation.nowarn
import scala.annotation.tailrec
import scala.language.implicitConversions

/** Represents a computation that may perform effects before producing a value.
  *
  * The pending type (`<`) is the core abstraction in Kyo for representing effectful computations. It captures a computation that will
  * eventually produce a value of type `A` while potentially performing effects of type `S` along the way.
  *
  * For example, in `Int < (Abort[String] & Emit[Log])`:
  *   - `Int` is the value that will be produced
  *   - `Abort[String]` indicates the computation may fail with a String error
  *   - `Emit[Log]` indicates the computation may emit Log values during execution
  *   - The `&` shows that both effects may occur in this computation in any order
  *
  * This type allows Kyo to track effects at compile time and ensure they are properly handled. The effects are accumulated in the type
  * parameter `S` as an intersection type (`&`). Because intersection types are unordered, `Abort[String] & Emit[Log]` is equivalent to
  * `Emit[Log] & Abort[String]` - the order in which effects appear in the type does not determine the order in which they execute.
  *
  * The pending type has a single fundamental operation - the monadic bind, which is exposed as both `map` and `flatMap` (for
  * for-comprehension support). Plain values are automatically lifted into the effect context, which means `map` can serve as both `map` and
  * `flatMap`. This allows writing effectful code typically without having to distinguish map from flatMap or manually lifting values.
  */
opaque type <[+A, -S] = A | Kyo[A, S]

object `<`:

    extension [A, S](inline v: A < S)

        /** Maps the value produced by this computation to a new computation and flattens the result. This is the monadic bind operation for
          * the pending type.
          *
          * Note: Both `map` and `flatMap` have identical behavior in this API - they both act as the monadic bind. While `map` is the
          * recommended method to use, `flatMap` exists to support for-comprehension syntax in Scala.
          *
          * @param f
          *   The transformation function to apply to the result
          * @return
          *   A new computation producing the transformed value
          */
        inline def map[B, S2](inline f: Safepoint ?=> A => B < S2)(
            using
            inline _frame: Frame,
            inline flatA: WeakFlat[A],
            inline flatB: WeakFlat[B],
            inline safepoint: Safepoint
        ): B < (S & S2) =
            @nowarn("msg=anonymous") def mapLoop(v: A < S)(using Safepoint): B < (S & S2) =
                v match
                    case kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked =>
                        new KyoContinue[IX, OX, EX, Any, B, S & S2](kyo):
                            def frame = _frame
                            def apply(v: OX[Any], context: Context)(using Safepoint) =
                                mapLoop(kyo(v, context))
                    case v =>
                        val value = v.asInstanceOf[A]
                        Safepoint.handle(value)(
                            suspend = mapLoop(value),
                            continue = f(value)
                        )
            mapLoop(v)
        end map

        /** Maps the value produced by this computation to a new computation and flattens the result.
          *
          * This method exists to support for-comprehension syntax in Scala. It is identical to `map` and `map` should be preferred when not
          * using for-comprehensions.
          *
          * @param f
          *   The function producing the next computation
          * @return
          *   A computation producing the final result
          */
        inline def flatMap[B, S2](inline f: Safepoint ?=> A => B < S2)(
            using
            inline frame: Frame,
            inline flatA: WeakFlat[A],
            inline flatB: WeakFlat[B]
        ): B < (S & S2) =
            map(v => f(v))

        /** Executes this computation, discards its result, and then executes another computation.
          *
          * @param f
          *   The computation to execute after this one
          * @return
          *   A computation producing the second result
          */
        inline def andThen[B, S2](inline f: Safepoint ?=> B < S2)(
            using
            inline frame: Frame,
            inline flatA: WeakFlat[A],
            inline flatB: WeakFlat[B]
        ): B < (S & S2) =
            map(_ => f)

        /** Executes this computation and discards its result.
          *
          * @return
          *   A computation that produces Unit
          */
        inline def unit(
            using
            inline _frame: Frame,
            inline flat: WeakFlat[A],
            inline safepoint: Safepoint
        ): Unit < S =
            @nowarn("msg=anonymous") def unitLoop(v: A < S)(using Safepoint): Unit < S =
                v match
                    case kyo: KyoSuspend[IX, OX, EX, Any, A, S] @unchecked =>
                        new KyoContinue[IX, OX, EX, Any, Unit, S](kyo):
                            def frame = _frame
                            def apply(v: OX[Any], context: Context)(using Safepoint) =
                                unitLoop(kyo(v, context))
                    case v =>
                        ()
            unitLoop(v)
        end unit

        /** Applies a sequence of transformations to this computation.
          */
        inline def pipe[B](inline f: (=> A < S) => B)(
            using inline flat: WeakFlat[A]
        ): B =
            def pipe1 = v
            f(pipe1)
        end pipe

        /** Applies a sequence of transformations to this computation.
          */
        inline def pipe[B, C](
            inline f1: A < S => B,
            inline f2: (=> B) => C
        )(using inline flat: WeakFlat[A]): C =
            def pipe2 = v.pipe(f1)
            f2(pipe2)
        end pipe

        /** Applies a sequence of transformations to this computation.
          */
        inline def pipe[B, C, D](
            inline f1: A < S => B,
            inline f2: (=> B) => C,
            inline f3: (=> C) => D
        )(using inline flat: WeakFlat[A]): D =
            def pipe3 = v.pipe(f1, f2)
            f3(pipe3)
        end pipe

        /** Applies a sequence of transformations to this computation.
          */
        inline def pipe[B, C, D, E](
            inline f1: A < S => B,
            inline f2: (=> B) => C,
            inline f3: (=> C) => D,
            inline f4: (=> D) => E
        )(using inline flat: WeakFlat[A]): E =
            def pipe4 = v.pipe(f1, f2, f3)
            f4(pipe4)
        end pipe

        /** Applies a sequence of transformations to this computation.
          */
        inline def pipe[B, C, D, E, F](
            inline f1: A < S => B,
            inline f2: (=> B) => C,
            inline f3: (=> C) => D,
            inline f4: (=> D) => E,
            inline f5: (=> E) => F
        )(using inline flat: WeakFlat[A]): F =
            def pipe5 = v.pipe(f1, f2, f3, f4)
            f5(pipe5)
        end pipe

        /** Applies a sequence of transformations to this computation.
          */
        inline def pipe[B, C, D, E, F, G](
            inline f1: A < S => B,
            inline f2: (=> B) => C,
            inline f3: (=> C) => D,
            inline f4: (=> D) => E,
            inline f5: (=> E) => F,
            inline f6: (=> F) => G
        )(using inline flat: WeakFlat[A]): G =
            def pipe6 = v.pipe(f1, f2, f3, f4, f5)
            f6(pipe6)
        end pipe

        private[kyo] inline def evalNow(using inline flat: Flat[A]): Maybe[A] =
            v match
                case kyo: Kyo[?, ?] => Maybe.empty
                case v              => Maybe(v.asInstanceOf[A])

    end extension

    extension [A, S, S2](v: A < S < S2)
        /** Flattens a nested pending computation into a single computation.
          *
          * @return
          *   A flattened computation of type `A` with combined effects `S & S2`
          */
        def flatten(using _frame: Frame): A < (S & S2) =
            def flattenLoop(v: A < S < S2)(using Safepoint): A < (S & S2) =
                v match
                    case kyo: KyoSuspend[IX, OX, EX, Any, A < 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) =
                                flattenLoop(kyo(v, context))
                    case v =>
                        v.asInstanceOf[A]
            flattenLoop(v)

    extension [A](inline v: A < Any)

        /** Evaluates a pending computation that has no remaining effects (effect type is `Any`).
          *
          * This method can only be called on computations where all effects have been handled, leaving only pure computation steps. It will
          * execute the computation and return the final result.
          *
          * @return
          *   The final result of type `A` after evaluating the computation
          * @throws IllegalStateException
          *   if unhandled effects remain in the computation
          */
        inline def eval(using inline frame: Frame, inline flat: Flat[A]): A =
            @tailrec def evalLoop(kyo: A < Any)(using Safepoint): A =
                kyo match
                    case kyo: KyoSuspend[Const[Unit], Const[Unit], Defer, Any, A, Any] @unchecked
                        if kyo.tag =:= Tag[Defer] =>
                        evalLoop(kyo((), Context.empty))
                    case kyo: Kyo[A, Any] @unchecked =>
                        bug.failTag(kyo, Tag[Any])
                    case v =>
                        v.asInstanceOf[A]
                end match
            end evalLoop
            Safepoint.eval(evalLoop(v))
        end eval
    end extension

    implicit private[kernel] inline def fromKyo[A, S](v: Kyo[A, S]): A < S = v

    /** Converts a plain value to an effectful computation. */
    implicit inline def lift[A, S](v: A)(using inline flat: WeakFlat[A]): A < S = v

    /** Converts a pure single-argument function to an effectful computation. */
    implicit inline def liftPureFunction1[A1, B](inline f: A1 => B)(
        using inline flat: WeakFlat[B]
    ): A1 => B < Any =
        a1 => f(a1)

    /** Converts a pure two-argument function to an effectful computation. */
    implicit inline def liftPureFunction2[A1, A2, B](inline f: (A1, A2) => B)(
        using inline flat: WeakFlat[B]
    ): (A1, A2) => B < Any =
        (a1, a2) => f(a1, a2)

    /** Converts a pure three-argument function to an effectful computation. */
    implicit inline def liftPureFunction3[A1, A2, A3, B](inline f: (A1, A2, A3) => B)(
        using inline flat: WeakFlat[B]
    ): (A1, A2, A3) => B < Any =
        (a1, a2, a3) => f(a1, a2, a3)

    /** Converts a pure four-argument function to an effectful computation. */
    implicit inline def liftPureFunction4[A1, A2, A3, A4, B](inline f: (A1, A2, A3, A4) => B)(
        using inline flat: WeakFlat[B]
    ): (A1, A2, A3, A4) => B < Any =
        (a1, a2, a3, a4) => f(a1, a2, a3, a4)

    /** Converts a pure five-argument function to an effectful computation. */
    implicit inline def liftPureFunction5[A1, A2, A3, A4, A5, B](inline f: (A1, A2, A3, A4, A5) => B)(
        using inline flat: WeakFlat[B]
    ): (A1, A2, A3, A4, A5) => B < Any =
        (a1, a2, a3, a4, a5) => f(a1, a2, a3, a4, a5)

    /** Converts a pure six-argument function to an effectful computation. */
    implicit inline def liftPureFunction6[A1, A2, A3, A4, A5, A6, B](inline f: (A1, A2, A3, A4, A5, A6) => B)(
        using inline flat: WeakFlat[B]
    ): (A1, A2, A3, A4, A5, A6) => B < Any =
        (a1, a2, a3, a4, a5, a6) => f(a1, a2, a3, a4, a5, a6)
end `<`




© 2015 - 2025 Weber Informatics LLC | Privacy Policy