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

kyo.Abort.scala Maven / Gradle / Ivy

The newest version!
package kyo

import kernel.ArrowEffect
import kyo.Result.*
import kyo.Tag
import kyo.kernel.Effect
import kyo.kernel.Reducible
import scala.annotation.targetName

/** The Abort effect allows for short-circuiting computations with failure values. This effect is used for handling errors and early
  * termination scenarios in a functional manner and handle thrown exceptions.
  *
  * @tparam E
  *   The type of the failure value
  */
sealed trait Abort[-E] extends ArrowEffect[Const[Error[E]], Const[Unit]]

object Abort:

    import internal.*

    given eliminateAbort: Reducible.Eliminable[Abort[Nothing]] with {}
    private inline def erasedTag[E]: Tag[Abort[E]] = Tag[Abort[Any]].asInstanceOf[Tag[Abort[E]]]

    /** Fails the computation with the given value.
      *
      * @param value
      *   The failure value
      * @return
      *   A computation that immediately fails with the given value
      */
    inline def fail[E](inline value: E)(using inline frame: Frame): Nothing < Abort[E] = error(Fail(value))

    /** Fails the computation with a panic value (unchecked exception).
      *
      * @param ex
      *   The exception to use as a panic value
      * @return
      *   A computation that immediately fails with the given exception
      */
    inline def panic[E](inline ex: Throwable)(using inline frame: Frame): Nothing < Abort[E] = error(Panic(ex))

    /** Fails the computation with the given error value (failure or panic).
      *
      * @param e
      *   The error value to fail with
      * @return
      *   A computation that immediately fails with the given error value
      */
    inline def error[E](inline e: Error[E])(using inline frame: Frame): Nothing < Abort[E] =
        ArrowEffect.suspendAndMap[Any](erasedTag[E], e)(_ => ???)

    /** Fails the computation if the condition is true.
      *
      * @param b
      *   The condition to check
      * @param value
      *   The failure value to use if the condition is true
      * @return
      *   A unit computation that may fail if the condition is true
      */
    inline def when[E](b: Boolean)(inline value: => E)(using inline frame: Frame): Unit < Abort[E] =
        ensuring(!b, ())(value)

    /** Ensures a condition is met before returning the provided result.
      *
      * @param cond
      *   The condition to check
      * @param result
      *   The result to return if the condition is true
      * @param value
      *   The failure value to use if the condition is false
      * @tparam A
      *   The type of the result
      * @tparam E
      *   The type of the failure value
      * @return
      *   A computation that succeeds with the result if the condition is true, or fails with the given value if it's false
      */
    inline def ensuring[A, E](cond: Boolean, result: => A)(inline value: => E)(using inline frame: Frame): A < Abort[E] =
        if !cond then fail(value)
        else result

    final class GetOps[E >: Nothing](dummy: Unit) extends AnyVal:

        /** Lifts an Either into the Abort effect.
          *
          * @param either
          *   The Either to lift
          * @return
          *   A computation that succeeds with the Right value or fails with the Left value
          */
        inline def apply[A](either: Either[E, A])(using inline frame: Frame): A < Abort[E] =
            either match
                case Right(value) => value
                case Left(value)  => fail(value)

        /** Lifts an Option into the Abort effect with Absent as the failure value.
          *
          * @param opt
          *   The Option to lift
          * @return
          *   A computation that succeeds with the Some value or fails with Absent
          */
        inline def apply[A](opt: Option[A])(using inline frame: Frame): A < Abort[Absent] =
            opt match
                case None    => fail(Absent)
                case Some(v) => v

        /** Lifts a scala.util.Try into the Abort effect.
          *
          * @param e
          *   The Try to lift
          * @return
          *   A computation that succeeds with the Success value or fails with the Failure exception
          */
        inline def apply[A](e: scala.util.Try[A])(using inline frame: Frame): A < Abort[Throwable] =
            e match
                case scala.util.Success(t) => t
                case scala.util.Failure(v) => fail(v)

        /** Lifts a Result into the Abort effect.
          *
          * @param r
          *   The Result to lift
          * @return
          *   A computation that succeeds with the Success value or fails with the Failure value
          */
        inline def apply[E, A](r: Result[E, A])(using inline frame: Frame): A < Abort[E] =
            r.fold {
                case e: Fail[E] => fail(e.error)
                case Panic(ex)  => Abort.panic(ex)
            }(identity)

        /** Lifts a Maybe into the Abort effect.
          *
          * @param m
          *   The Maybe to lift
          * @return
          *   A computation that succeeds with the Present value or fails with Absent
          */
        @targetName("maybe")
        inline def apply[A](m: Maybe[A])(using inline frame: Frame): A < Abort[Absent] =
            m.fold(fail(Absent))(identity)
    end GetOps

    /** Operations for lifting various types into the Abort effect.
      *
      * @tparam E
      *   The failure type of the Abort effect being run
      */
    inline def get[E >: Nothing]: GetOps[E] = GetOps(())

    final class RunOps[E >: Nothing](dummy: Unit) extends AnyVal:
        /** Runs an Abort effect, converting it to a Result.
          *
          * @param v
          *   The computation to run
          * @tparam A
          *   The success type of the computation
          * @tparam S
          *   The effect type of the computation
          * @tparam ER
          *   Any remaining Abort effects after running this one
          * @return
          *   A Result containing either the success value or the failure value, wrapped in the remaining effects
          */
        def apply[A: Flat, S, ER](v: => A < (Abort[E | ER] & S))(
            using
            frame: Frame,
            ct: SafeClassTag[E],
            reduce: Reducible[Abort[ER]]
        ): Result[E, A] < (S & reduce.SReduced) =
            reduce {
                ArrowEffect.handleCatching[
                    Const[Error[E]],
                    Const[Unit],
                    Abort[E],
                    Result[E, A],
                    Result[E, A],
                    Abort[ER] & S,
                    Abort[ER] & S,
                    Any
                ](
                    erasedTag[E],
                    v.map(Result.success[E, A](_))
                )(
                    accept = [C] =>
                        input =>
                            input.isPanic ||
                                input.asInstanceOf[Error[Any]].failure.exists(ct.accepts),
                    handle = [C] => (input, _) => input,
                    recover =
                        case ct(fail) if ct <:< SafeClassTag[Throwable] =>
                            Result.fail(fail)
                        case fail =>
                            Result.panic(fail)
                )
            }
        end apply
    end RunOps

    /** Runs an Abort effect. This operation handles the Abort effect, converting it into a Result type.
      */
    inline def run[E]: RunOps[E] = RunOps(())

    final class RecoverOps[E](dummy: Unit) extends AnyVal:

        /** Recovers from an Abort failure by applying the provided function.
          *
          * This method allows you to handle failures in an Abort effect and potentially continue the computation with a new value. It only
          * handles failures of type E and leaves panics unhandled (Abort[Nothing]).
          *
          * @param onFail
          *   A function that takes the failure value of type E and returns a new computation
          * @param v
          *   The original computation that may fail
          * @return
          *   A computation that either succeeds with the original value or the recovered value
          */
        def apply[A: Flat, B: Flat, S, ER](onFail: E => B < S)(v: => A < (Abort[E | ER] & S))(
            using
            frame: Frame,
            ct: SafeClassTag[E],
            reduce: Reducible[Abort[ER]]
        ): (A | B) < (S & Abort[ER]) =
            ArrowEffect.handleCatching[
                Const[Error[E]],
                Const[Unit],
                Abort[E],
                A | B,
                A | B,
                Abort[ER] & S,
                Abort[ER] & S,
                Any
            ](
                erasedTag[E],
                v
            )(
                accept = [C] =>
                    input =>
                        input.asInstanceOf[Error[Any]].failure.exists(ct.accepts),
                handle = [C] =>
                    (input, _) =>
                        (input: @unchecked) match
                            case Fail(e)      => onFail(e)
                            case panic: Panic => Abort.error(panic),
                recover =
                    case ct(fail) if ct <:< SafeClassTag[Throwable] =>
                        onFail(fail)
                    case ex =>
                        Abort.panic(ex)
            )

        /** Recovers from an Abort failure or panic by applying the provided functions.
          *
          * This method allows you to handle both failures and panics in an Abort effect. It provides separate handlers for failures of type
          * E and for panics (Throwables).
          *
          * @param onFail
          *   A function that takes the failure value of type E and returns a new computation
          * @param onPanic
          *   A function that takes a Throwable and returns a new computation
          * @param v
          *   The original computation that may fail or panic
          * @return
          *   A computation that either succeeds with the original value or the recovered value
          */
        def apply[A: Flat, B: Flat, S, ER](onFail: E => B < S, onPanic: Throwable => B < S)(v: => A < (Abort[E | ER] & S))(
            using
            frame: Frame,
            ct: SafeClassTag[E],
            reduce: Reducible[Abort[ER]]
        ): (A | B) < (S & reduce.SReduced) =
            reduce {
                ArrowEffect.handleCatching[
                    Const[Error[E]],
                    Const[Unit],
                    Abort[E],
                    A | B,
                    A | B,
                    Abort[ER] & S,
                    Abort[ER] & S,
                    Any
                ](
                    erasedTag[E],
                    v
                )(
                    accept = [C] =>
                        input =>
                            input.isPanic ||
                                input.asInstanceOf[Error[Any]].failure.exists(ct.accepts),
                    handle = [C] =>
                        (input, _) =>
                            (input: @unchecked) match
                                case Fail(e)   => onFail(e)
                                case Panic(ex) => onPanic(ex),
                    recover =
                        case ct(fail) if ct <:< SafeClassTag[Throwable] =>
                            onFail(fail)
                        case ex =>
                            onPanic(ex)
                )
            }
        end apply
    end RecoverOps

    /** Provides recovery operations for Abort effects.
      */
    inline def recover[E]: RecoverOps[E] = RecoverOps(())

    final class CatchingOps[E <: Throwable](dummy: Unit) extends AnyVal:
        /** Catches exceptions of type E and converts them to Abort failures.
          *
          * @param v
          *   The computation to run and catch exceptions from
          * @tparam A
          *   The return type of the computation
          * @tparam S
          *   The effect type of the computation
          * @return
          *   A computation that may fail with an Abort[E] if an exception of type E is caught
          */
        def apply[A, S](v: => A < S)(
            using
            ct: SafeClassTag[E],
            frame: Frame
        ): A < (Abort[E] & S) =
            Effect.catching(v) {
                case ct(ex) => Abort.fail(ex)
                case ex     => Abort.panic(ex)
            }
    end CatchingOps

    /** Catches exceptions and converts them to Abort failures. This is useful for integrating exception-based code with the Abort effect.
      */
    inline def catching[E <: Throwable]: CatchingOps[E] = CatchingOps(())

end Abort




© 2015 - 2025 Weber Informatics LLC | Privacy Policy