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

kyo.Poll.scala Maven / Gradle / Ivy

The newest version!
package kyo

import kyo.kernel.ArrowEffect

/** Represents polling values from a data source with backpressure control.
  *
  * Poll is used to consume values while maintaining flow control through acknowledgements. Each poll operation takes an Ack that determines
  * how many values can be consumed, and returns Maybe[V] indicating whether a value was available.
  *
  * Key behaviors:
  *   - Each poll operation requires an Ack value that signals the consumer's readiness to receive more data
  *   - Poll returns Maybe[V], where:
  *     - Present(v) indicates a successful poll with value v
  *     - Absent indicates the end of the stream (no more values will be available)
  *   - Once Absent is received, the consumer should stop polling as the stream has terminated
  *   - Backpressure is maintained through the Ack responses:
  *     - Continue signals readiness to receive more values
  *     - Stop indicates the consumer wants to pause receiving values
  *
  * The effect enables building streaming data pipelines with controlled consumption rates. Handlers can process values at their own pace by
  * returning appropriate Ack responses, while respecting stream termination signals.
  *
  * @tparam V
  *   The type of values being polled from the data source.
  */
sealed trait Poll[V] extends ArrowEffect[Const[Ack], Const[Maybe[V]]]

object Poll:

    /** Attempts to poll a single value with acknowledgment.
      *
      * Requests a value from the data source while providing an acknowledgment to control flow. The acknowledgment signals the source about
      * consumer readiness and backpressure.
      *
      * @param ack
      *   The acknowledgment controlling value emission
      * @return
      *   Maybe containing the polled value if available, or Absent if stream ended
      */
    inline def one[V](inline ack: Ack)(
        using
        inline tag: Tag[Poll[V]],
        inline frame: Frame
    ): Maybe[V] < Poll[V] =
        ArrowEffect.suspend[Unit](tag, ack)

    /** Attempts to poll a single value with Continue acknowledgement.
      *
      * @return
      *   A computation that produces Maybe containing the polled value if available
      */
    inline def one[V](
        using
        inline tag: Tag[Poll[V]],
        inline frame: Frame
    ): Maybe[V] < Poll[V] =
        one(Ack.Continue())

    final class ApplyOps[V](dummy: Unit) extends AnyVal:

        /** Processes polled values with a function. Values are processed until n is reached or the stream completes.
          *
          * @param n
          *   Maximum number of values to process
          * @param f
          *   Function to apply to each value
          * @return
          *   A computation that processes values until completion or limit reached
          */
        def apply[S](n: Int)(f: V => Unit < S)(using tag: Tag[Poll[V]], frame: Frame): Unit < (Poll[V] & S) =
            Loop.indexed { idx =>
                if idx == n then Loop.done
                else
                    Poll.andMap[V] {
                        case Present(v) => f(v).map(Loop.continue)
                        case Absent     => Loop.done
                    }
            }

        /** Processes polled values with a function until the stream completes.
          *
          * @param f
          *   Function to apply to each value
          * @return
          *   A computation that processes values until completion
          */
        def apply[S](f: V => Unit < S)(using tag: Tag[Poll[V]], frame: Frame): Unit < (Poll[V] & S) =
            Loop(()) { _ =>
                Poll.andMap[V] {
                    case Present(v) => f(v).map(Loop.continue)
                    case Absent     => Loop.done
                }
            }
    end ApplyOps

    /** Creates an instance of ApplyOps for processing polled values.
      *
      * @return
      *   An instance of ApplyOps
      */
    inline def apply[V]: ApplyOps[V] = ApplyOps(())

    final case class AndMapOps[V](dummy: Unit) extends AnyVal:

        /** Applies a function to the result of polling with Continue acknowledgement.
          *
          * @param f
          *   Function to apply to the polled result
          * @return
          *   A computation that applies the function to the polled result
          */
        inline def apply[A, S](f: Maybe[V] => A < S)(
            using
            inline tag: Tag[Poll[V]],
            inline frame: Frame
        ): A < (Poll[V] & S) =
            apply(Ack.Continue())(f)

        /** Applies a function to the result of polling with the given acknowledgement.
          *
          * @param ack
          *   The acknowledgement controlling value emission
          * @param f
          *   Function to apply to the polled result
          * @return
          *   A computation that applies the function to the polled result
          */
        inline def apply[A, S](inline ack: Ack)(f: Maybe[V] => A < S)(
            using
            inline tag: Tag[Poll[V]],
            inline frame: Frame
        ): A < (Poll[V] & S) =
            ArrowEffect.suspendAndMap[Unit](tag, ack)(f)
    end AndMapOps

    def andMap[V]: AndMapOps[V] = AndMapOps(())

    final class FoldOps[V](dummy: Unit) extends AnyVal:

        /** Folds over polled values with an accumulator.
          *
          * Processes values from the stream by combining them with an accumulator value. Continues until the stream ends, allowing stateful
          * processing of the sequence.
          *
          * @param acc
          *   Initial accumulator value
          * @param f
          *   Function to combine accumulator with each value
          * @return
          *   Final accumulator value after processing all values
          */
        def apply[A, S](acc: A)(f: (A, V) => A < S)(
            using
            tag: Tag[Poll[V]],
            frame: Frame
        ): A < (Poll[V] & S) =
            Loop(acc) { state =>
                Poll.andMap[V] {
                    case Absent     => Loop.done(state)
                    case Present(v) => f(state, v).map(Loop.continue(_))
                }
            }
    end FoldOps

    inline def fold[V]: FoldOps[V] = FoldOps(())

    /** Runs a Poll effect using the provided sequence of values.
      *
      * The Poll effect will consume values from the input chunk sequentially. Once all elements in the chunk have been consumed, subsequent
      * polls will receive Maybe.Absent, signaling the end of the stream.
      *
      * @param inputs
      *   The sequence of values to provide to the Poll effect
      * @param v
      *   The computation requiring Poll values
      * @return
      *   The result of running the Poll computation with the provided values
      */
    def run[V, A: Flat, S](inputs: Chunk[V])(v: A < (Poll[V] & S))(
        using
        tag: Tag[Poll[V]],
        frame: Frame
    ): A < S =
        ArrowEffect.handleState(tag, inputs, v)(
            [C] =>
                (input, state, cont) =>
                    input match
                        case Ack.Stop => (Chunk.empty, cont(Absent))
                        case _        => (state.drop(1), cont(state.headMaybe))
        )

    final case class RunFirstOps[V](dummy: Unit) extends AnyVal:

        /** Runs a Poll effect with a single input value, stopping after the first poll operation.
          *
          * This method provides a single input value to the Poll effect and stops after the first poll. It returns a tuple containing:
          *   - An Ack value indicating whether to continue or stop
          *   - A continuation function that can process the Maybe[V] result of the poll
          *
          * @param v
          *   The computation requiring Poll values
          * @return
          *   A tuple containing the acknowledgement and a continuation function that processes the poll result
          */
        def apply[A: Flat, S](v: A < (Poll[V] & S))(
            using
            tag: Tag[Poll[V]],
            frame: Frame
        ): (Ack, Maybe[V] => A < (Poll[V] & S)) < S =
            ArrowEffect.handleFirst(tag, v)(
                handle = [C] =>
                    (input, cont) =>
                        // Effect found, return the input an continuation
                        (input, cont),
                done = r =>
                    // Effect not found, return empty input and a placeholder continuation
                    // that returns the result of the computation
                    (Ack.Stop, (_: Maybe[V]) => r: A < (Poll[V] & S))
            )
    end RunFirstOps

    def runFirst[V]: RunFirstOps[V] = RunFirstOps(())

    /** Connects an emitting source to a polling consumer with flow control.
      *
      * The emitting source produces values that are consumed by the polling computation in a demand-driven way. The polling consumer
      * controls the flow rate by sending acknowledgments (Ack) to indicate readiness for more data. The emitter responds to these signals
      * to implement backpressure.
      *
      * The flow continues until either:
      *   - The emitter completes, signaling end-of-stream to the consumer via Maybe.Absent
      *   - The consumer sends Ack.Stop to terminate consumption
      *   - Both sides complete naturally
      *
      * @param emit
      *   The emitting computation that produces values
      * @param poll
      *   The polling computation that consumes values
      * @return
      *   A tuple containing results from both the emitter and poller
      */
    def run[V, A: Flat, B: Flat, S, S2](emit: A < (Emit[V] & S))(poll: B < (Poll[V] & S2))(
        using
        emitTag: Tag[Emit[V]],
        pollTag: Tag[Poll[V]],
        frame: Frame
    ): (A, B) < (S & S2) =
        // Start by handling the first emission
        Loop(emit, poll) { (emit, poll) =>
            ArrowEffect.handleFirst(emitTag, emit)(
                handle = [C] =>
                    (emitted, emitCont) =>
                        // Once we have an emitted value, handle the first poll operation
                        // This creates the demand-driven cycle between emit and poll
                        ArrowEffect.handleFirst(pollTag, poll)(
                            handle = [C2] =>
                                (ack, pollCont) =>
                                    // Continue the emit-poll cycle:
                                    // 1. Pass the ack back to emitter to control flow
                                    // 2. Pass the emitted value to poller for consumption
                                    // 3. Recursively continue the cycle
                                    Loop.continue(emitCont(ack), pollCont(Maybe(emitted))),
                            // Poll.run(emitCont(ack))(pollCont(Maybe(emitted))),
                            done = b =>
                                // Poller completed early (e.g., received all needed values)
                                // Discard remaining emit operations
                                Emit.runDiscard(emitCont(Ack.Stop)).map(a => Loop.done((a, b)))
                    ),
                done = a =>
                    // Emitter completed (no more values to emit)
                    // Run remaining poll operations with empty chunk to signal completion
                    Poll.run(Chunk.empty)(poll).map(b => Loop.done((a, b)))
            )
        }
    end run

end Poll




© 2015 - 2025 Weber Informatics LLC | Privacy Policy