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

kyo.Kyo.scala Maven / Gradle / Ivy

The newest version!
package kyo

import kernel.Loop
import kyo.kernel.Safepoint
import scala.annotation.tailrec

/** Object containing utility functions for working with Kyo effects. */
object Kyo:

    /** Explicitly creates a pure effect that produces the given value.
      *
      * While pure values are automatically lifted into Kyo computations in most cases, this method can be useful in specific scenarios,
      * such as in if/else expressions, to help with type inference.
      *
      * @tparam A
      *   The type of the value
      * @tparam S
      *   The effect context (can be Any)
      * @param v
      *   The value to lift into the effect context
      * @return
      *   A computation that produces the given value
      */
    inline def pure[A, S](inline v: A): A < S = v

    /** Zips two effects into a tuple.
      *
      * @param v1
      *   The first effect
      * @param v2
      *   The second effect
      * @return
      *   A new effect that produces a tuple of the results
      */
    def zip[A1, A2, S](v1: A1 < S, v2: A2 < S)(using Frame): (A1, A2) < S =
        v1.map(t1 => v2.map(t2 => (t1, t2)))

    /** Zips three effects into a tuple.
      *
      * @param v1
      *   The first effect
      * @param v2
      *   The second effect
      * @param v3
      *   The third effect
      * @return
      *   A new effect that produces a tuple of the results
      */
    def zip[A1, A2, A3, S](v1: A1 < S, v2: A2 < S, v3: A3 < S)(using Frame): (A1, A2, A3) < S =
        v1.map(t1 => v2.map(t2 => v3.map(t3 => (t1, t2, t3))))

    /** Zips four effects into a tuple.
      *
      * @param v1
      *   The first effect
      * @param v2
      *   The second effect
      * @param v3
      *   The third effect
      * @param v4
      *   The fourth effect
      * @return
      *   A new effect that produces a tuple of the results
      */
    def zip[A1, A2, A3, A4, S](v1: A1 < S, v2: A2 < S, v3: A3 < S, v4: A4 < S)(using Frame): (A1, A2, A3, A4) < S =
        v1.map(t1 => v2.map(t2 => v3.map(t3 => v4.map(t4 => (t1, t2, t3, t4)))))

    /** Applies an effect-producing function to each element of a sequence.
      *
      * @param seq
      *   The input sequence
      * @param f
      *   The effect-producing function to apply to each element
      * @return
      *   A new effect that produces a Chunk of results
      */
    def foreach[A, B, S, S2](seq: Seq[A])(f: Safepoint ?=> A => B < S2)(using Frame, Safepoint): Chunk[B] < (S & S2) =
        seq.knownSize match
            case 0 => Chunk.empty
            case 1 => f(seq(0)).map(Chunk(_))
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop(seq, Chunk.empty[B]) { (seq, acc) =>
                            seq match
                                case Nil          => Loop.done(acc)
                                case head :: tail => f(head).map(u => Loop.continue(tail, acc.append(u)))
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed(Chunk.empty[B]) { (idx, acc) =>
                            if idx == size then Loop.done(acc)
                            else f(indexed(idx)).map(u => Loop.continue(acc.append(u)))
                        }
                end match
    end foreach

    /** Applies an effect-producing function to each element of a sequence along with its index.
      *
      * @param seq
      *   The input sequence
      * @param f
      *   The effect-producing function to apply to each element and its index
      * @return
      *   A new effect that produces a Chunk of results
      */
    def foreachIndexed[A, B, S, S2](seq: Seq[A])(f: Safepoint ?=> (Int, A) => B < S2)(using Frame, Safepoint): Chunk[B] < (S & S2) =
        seq.knownSize match
            case 0 => Chunk.empty
            case 1 => f(0, seq(0)).map(Chunk(_))
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop.indexed(seq, Chunk.empty[B]) { (idx, seq, acc) =>
                            seq match
                                case Nil          => Loop.done(acc)
                                case head :: tail => f(idx, head).map(u => Loop.continue(tail, acc.append(u)))
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed(Chunk.empty[B]) { (idx, acc) =>
                            if idx == size then Loop.done(acc)
                            else f(idx, indexed(idx)).map(u => Loop.continue(acc.append(u)))
                        }
                end match
    end foreachIndexed

    /** Applies an effect-producing function to each element of a sequence, discarding the results.
      *
      * @param seq
      *   The input sequence
      * @param f
      *   The effect-producing function to apply to each element
      * @return
      *   A new effect that produces Unit
      */
    def foreachDiscard[A, B, S](seq: Seq[A])(f: Safepoint ?=> A => Unit < S)(using Frame, Safepoint): Unit < S =
        seq.knownSize match
            case 0 =>
            case 1 => f(seq(0))
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop(seq) {
                            case Nil          => Loop.done
                            case head :: tail => f(head).andThen(Loop.continue(tail))
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed { idx =>
                            if idx == size then Loop.done
                            else f(indexed(idx)).andThen(Loop.continue)
                        }
                end match
        end match
    end foreachDiscard

    /** Filters elements of a sequence based on an effect-producing predicate.
      *
      * @param seq
      *   The input sequence
      * @param f
      *   The effect-producing predicate function
      * @return
      *   A new effect that produces a Chunk of filtered elements
      */
    def filter[A, S, S2](seq: Seq[A])(f: Safepoint ?=> A => Boolean < S2)(using Frame, Safepoint): Chunk[A] < (S & S2) =
        seq.knownSize match
            case 0 => Chunk.empty
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop(seq, Chunk.empty[A]) { (seq, acc) =>
                            seq match
                                case Nil => Loop.done(acc)
                                case head :: tail =>
                                    f(head).map {
                                        case true  => Loop.continue(tail, acc.append(head))
                                        case false => Loop.continue(tail, acc)
                                    }
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed(Chunk.empty[A]) { (idx, acc) =>
                            if idx == size then Loop.done(acc)
                            else
                                val curr = indexed(idx)
                                f(curr).map {
                                    case true  => Loop.continue(acc.append(curr))
                                    case false => Loop.continue(acc)
                                }
                        }
                end match
    end filter

    /** Folds over a sequence with an effect-producing function.
      *
      * @param seq
      *   The input sequence
      * @param acc
      *   The initial accumulator value
      * @param f
      *   The effect-producing folding function
      * @return
      *   A new effect that produces the final accumulated value
      */
    def foldLeft[A, B, S](seq: Seq[A])(acc: B)(f: Safepoint ?=> (B, A) => B < S)(using Frame, Safepoint): B < S =
        seq.knownSize match
            case 0 => acc
            case 1 => f(acc, seq(0))
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop(seq, acc) { (seq, acc) =>
                            seq match
                                case Nil          => Loop.done(acc)
                                case head :: tail => f(acc, head).map(Loop.continue(tail, _))
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed(acc) { (idx, acc) =>
                            if idx == size then Loop.done(acc)
                            else f(acc, indexed(idx)).map(Loop.continue(_))
                        }
                end match
        end match
    end foldLeft

    /** Collects the results of a sequence of effects into a single effect.
      *
      * @param seq
      *   The sequence of effects
      * @return
      *   A new effect that produces a Chunk of results
      */
    def collect[A, S](seq: Seq[A < S])(using Frame, Safepoint): Chunk[A] < S =
        seq.knownSize match
            case 0 => Chunk.empty
            case 1 => seq(0).map(Chunk(_))
            case _ =>
                seq match
                    case seq: List[A < S] =>
                        Loop(seq, Chunk.empty[A]) { (seq, acc) =>
                            seq match
                                case Nil          => Loop.done(acc)
                                case head :: tail => head.map(u => Loop.continue(tail, acc.append(u)))
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed(Chunk.empty[A]) { (idx, acc) =>
                            if idx == size then Loop.done(acc)
                            else indexed(idx).map(u => Loop.continue(acc.append(u)))
                        }
                end match
    end collect

    /** Collects the results of a sequence of effects, discarding the results.
      *
      * @param seq
      *   The sequence of effects
      * @return
      *   A new effect that produces Unit
      */
    def collectDiscard[A, S](seq: Seq[A < S])(using Frame, Safepoint): Unit < S =
        seq.knownSize match
            case 0 =>
            case 1 => seq(0).unit
            case _ =>
                seq match
                    case seq: List[A < S] =>
                        Loop(seq) { seq =>
                            seq match
                                case Nil          => Loop.done
                                case head :: tail => head.map(_ => Loop.continue(tail))
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed { idx =>
                            if idx == size then Loop.done
                            else indexed(idx).map(_ => Loop.continue)
                        }
                end match
    end collectDiscard

    /** Finds the first element in a sequence that satisfies a predicate.
      *
      * @param seq
      *   The input sequence
      * @param f
      *   The effect-producing predicate function
      * @return
      *   A new effect that produces Maybe of the first matching element
      */
    def findFirst[A, B, S](seq: Seq[A])(f: Safepoint ?=> A => Maybe[B] < S)(using Frame, Safepoint): Maybe[B] < S =
        seq.knownSize match
            case 0 => Maybe.empty
            case 1 => f(seq(0))
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop(seq) { seq =>
                            seq match
                                case Nil => Loop.done(Maybe.empty)
                                case head :: tail =>
                                    f(head).map {
                                        case Absent     => Loop.continue(tail)
                                        case Present(v) => Loop.done(Maybe(v))
                                    }
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed { idx =>
                            if idx == size then Loop.done(Maybe.empty)
                            else
                                f(indexed(idx)).map {
                                    case Absent     => Loop.continue
                                    case Present(v) => Loop.done(Maybe(v))
                                }
                        }
                end match
    end findFirst

    /** Takes elements from a sequence while a predicate holds true.
      *
      * @param seq
      *   The input sequence
      * @param f
      *   The effect-producing predicate function
      * @return
      *   A new effect that produces a Chunk of taken elements
      */
    def takeWhile[A, S](seq: Seq[A])(f: Safepoint ?=> A => Boolean < S)(using Frame, Safepoint): Chunk[A] < S =
        seq.knownSize match
            case 0 => Chunk.empty
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop(seq, Chunk.empty[A]) { (seq, acc) =>
                            seq match
                                case Nil => Loop.done(acc)
                                case head :: tail =>
                                    f(head).map {
                                        case true  => Loop.continue(tail, acc.append(head))
                                        case false => Loop.done(acc)
                                    }
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed(Chunk.empty[A]) { (idx, acc) =>
                            if idx == size then Loop.done(acc)
                            else
                                val curr = indexed(idx)
                                f(curr).map {
                                    case true  => Loop.continue(acc.append(curr))
                                    case false => Loop.done(acc)
                                }
                        }
                end match
    end takeWhile

    /** Drops elements from a sequence while a predicate holds true.
      *
      * @param seq
      *   The input sequence
      * @param f
      *   The effect-producing predicate function
      * @return
      *   A new effect that produces a Chunk of remaining elements
      */
    def dropWhile[A, S](seq: Seq[A])(f: Safepoint ?=> A => Boolean < S)(using Frame, Safepoint): Chunk[A] < S =
        seq.knownSize match
            case 0 => Chunk.empty
            case _ =>
                seq match
                    case seq: List[A] =>
                        Loop(seq) { seq =>
                            seq match
                                case Nil => Loop.done(Chunk.empty)
                                case head :: tail =>
                                    f(head).map {
                                        case true  => Loop.continue(tail)
                                        case false => Loop.done(Chunk.from(tail))
                                    }
                        }
                    case seq =>
                        val indexed = toIndexed(seq)
                        val size    = indexed.size
                        Loop.indexed { idx =>
                            if idx == size then Loop.done(Chunk.empty)
                            else
                                val curr = indexed(idx)
                                f(curr).map {
                                    case true  => Loop.continue
                                    case false => Loop.done(Chunk.from(indexed.drop(idx)))
                                }
                        }
                end match
    end dropWhile

    /** Creates a Chunk by repeating an effect-producing value.
      *
      * @param n
      *   The number of times to repeat the value
      * @param v
      *   The effect-producing value
      * @return
      *   A new effect that produces a Chunk of repeated values
      */
    def fill[A, S](n: Int)(v: Safepoint ?=> A < S)(using Frame, Safepoint): Chunk[A] < S =
        Loop.indexed(Chunk.empty[A]) { (idx, acc) =>
            if idx == n then Loop.done(acc)
            else v.map(t => Loop.continue(acc.append(t)))
        }

    private def toIndexed[A](seq: Seq[A]): Seq[A] =
        seq match
            case seq: IndexedSeq[A] => seq
            case seq                => Chunk.from(seq)

end Kyo




© 2015 - 2025 Weber Informatics LLC | Privacy Policy