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

kyo.Aspect.scala Maven / Gradle / Ivy

The newest version!
package kyo

import Aspect.*

/** Represents an aspect in the Kyo effect system.
  *
  * An aspect provides a way to modify or intercept behavior across multiple points in a program without directly changing the affected
  * code. It works by allowing users to provide implementations for abstract operations at runtime, similar to dependency injection but with
  * more powerful composition capabilities.
  *
  * Aspects are created using `Aspect.init` and are typically stored as vals at module level. Once initialized, an aspect can be used to
  * wrap computations that need to be modified, and its behavior can be customized using the `let` method to provide specific
  * implementations within a given scope. This pattern allows for clean separation between the definition of interceptable operations and
  * their actual implementations.
  *
  * Aspects support multi-shot continuations, meaning that cut implementations can invoke the continuation function multiple times or not at
  * all. This enables powerful control flow modifications like retry logic, fallback behavior, or conditional execution.
  *
  * Internally, aspects function as a form of reified ArrowEffect that can be stored, passed around, and modified at runtime. They maintain
  * state through a `Local` map of active implementations, allowing them to be dynamically activated and deactivated through operations like
  * `let` and `sandbox`.
  *
  * @tparam Input
  *   The input type constructor - what values the aspect can process
  * @tparam Output
  *   The output type constructor - what results the aspect can produce
  * @tparam S
  *   The effect type - what effects the aspect can perform
  */
final class Aspect[Input[_], Output[_], S] private[kyo] (
    default: Cut[Input, Output, S]
)(using Frame):

    /** Applies this aspect to transform a computation.
      *
      * When called, this method checks if there's a currently active cut for this aspect. If found, it applies that cut, otherwise falls
      * back to the default behavior.
      *
      * @param input
      *   The input value to transform
      * @param cont
      *   The continuation function to apply after the transformation
      * @tparam C
      *   The type parameter for the Input/Output type constructors
      */
    def apply[C](input: Input[C])(cont: Input[C] => Output[C] < S) =
        local.use { map =>
            map.get(this) match
                case Some(cut) =>
                    local.let(map - this) {
                        cut.asInstanceOf[Cut[Input, Output, S]](input, cont)
                    }
                case _ =>
                    default(input, cont)
        }

    /** Executes a computation in a sandboxed environment where this aspect's modifications are temporarily disabled.
      *
      * @param v
      *   The computation to sandbox
      * @tparam S
      *   Additional effects
      */
    def sandbox[A, S](v: A < S)(using Frame): A < S =
        local.use { map =>
            map.get(this) match
                case Some(_) =>
                    local.let(map - this) {
                        v
                    }
                case _ =>
                    v
        }

    /** Temporarily modifies this aspect's behavior for a given computation.
      *
      * @param a
      *   The new behavior to apply
      * @param v
      *   The computation to run with the modified behavior
      * @tparam A
      *   The computation result type
      * @tparam S2
      *   Additional effects
      */
    def let[A, S2](a: Cut[Input, Output, S])(v: A < S2)(using Frame): A < (S & S2) =
        local.use { map =>
            val cut =
                map.get(this) match
                    case Some(b: Cut[Input, Output, S] @unchecked) =>
                        Cut.andThen[Input, Output, S](b, a)
                    case _ =>
                        a
            local.let(map + (this -> cut.asInstanceOf[Cut[Const[Any], Const[Any], Any]]))(v)
        }

    /** Converts this aspect into a Cut.
      *
      * This method allows using an aspect directly as a cut, which can be useful when composing aspects or when a Cut type is explicitly
      * required. The resulting cut will have the same behavior as calling the aspect directly.
      *
      * @return
      *   A Cut that delegates to this aspect's implementation
      */
    def asCut: Cut[Input, Output, S] =
        [C] => (input, cont) => this(input)(cont)

end Aspect

object Aspect:

    private[kyo] val local = Local.init(Map.empty[Aspect[?, ?, ?], Cut[Const[Any], Const[Any], Any]])

    /** Represents a cut in the aspect-oriented programming model.
      *
      * A cut defines how an aspect modifies or intercepts computations. It provides a way to inject behavior before, after, or around the
      * intercepted operations.
      *
      * @tparam Input
      *   The input type constructor
      * @tparam Output
      *   The output type constructor
      * @tparam S
      *   The effect type
      */
    type Cut[I[_], O[_], S] = [C] => (I[C], I[C] => O[C] < S) => O[C] < S

    object Cut:

        /** Creates a cut from a function.
          *
          * @param cut
          *   The function to implement the cut
          */
        def apply[I[_], O[_], S](cut: Cut[I, O, S]): Cut[I, O, S] = cut

        /** Chains multiple cuts together into a single composite cut.
          *
          * @param head
          *   The first cut to apply
          * @param tail
          *   Additional cuts to apply in sequence
          */
        def andThen[I[_], O[_], S](a: Cut[I, O, S], b: Cut[I, O, S])(using Frame): Cut[I, O, S] =
            Cut[I, O, S](
                [C] => (input, cont) => a(input, b(_, cont))
            )
    end Cut

    /** Initializes a new aspect with default pass-through behavior.
      *
      * @tparam Input
      *   The input type constructor
      * @tparam Output
      *   The output type constructor
      * @tparam S
      *   The effect type
      */
    def init[I[_], O[_], S](using Frame): Aspect[I, O, S] =
        init([C] => (input: I[C], cont: I[C] => O[C] < S) => cont(input))

    /** Initializes a new aspect with a custom default behavior.
      *
      * @param default
      *   The default cut to use when no other cut is active
      */
    def init[I[_], O[_], S](default: Cut[I, O, S])(using Frame): Aspect[I, O, S] =
        new Aspect[I, O, S](default)

end Aspect




© 2015 - 2025 Weber Informatics LLC | Privacy Policy