Repository: getkyo/kyo
Pull request #1212: [feature][direct] asyncshift using loop (fixes #1211)
Author: login: ahoy-jon
Created: 2025-05-25T23:28:41Z | Merged: 2025-05-30T04:43:41Z

#1211 

First implementation that use `Kyo.foreach` / `Kyo.foldLeft` for `[a] =>> C[a] <:< Seq[a]`

| Benchmark           |   1024 |  65536 | 1048576 |
|---------------------|-------:|-------:|--------:|
| **deferIterable**   | 1.00x  | 1.00x  | 1.00x   |
| deferChunk          | 1.26x  | 1.39x  | 2.90x   |
| deferSeq            | 1.42x  | 1.40x  | 2.44x   |
| deferVector         | 0.92x  | 0.82x  | 1.76x   |
| deferVectorAsSeq    | 2.27x  | 1.90x  | 2.20x   |
| kyoForeach          | 2.48x  | 2.22x  | 3.63x   |

# Plan 
Support for:
- [x] `Chunk`
- [x] `Seq2Chunk` (if a `Seq`, return a `Chunk` instead)
- [x] `<:< Iterable`
- [x] more tests
- [x] test for Choice
- [ ] ~`MapOps`~
- [ ] ~`ArrayOps`~

Linked issue #1211: [feature]: optimize Seq support in defer
### Problem Statement

Currently, in `defer`
```scala
def f(i: Int): Int < Any = i + 1

defer:
  Chunk(1, 2, 3).map(i => f(i).now)
```
is using `SeqAsyncShift` https://github.com/dotty-cps-async/dotty-cps-async/blob/master/shared/src/main/scala/cps/runtime/SeqAsyncShift.scala.

There could be a performance issue compared to an implementation closer to `Kyo.foreach`.

### Proposed Solution

Use `AsyncShift` extensibility to use the `Kyo.Loop` mechanism.

Work in progress here: https://github.com/getkyo/kyo/compare/main...ahoy-jon:kyo:feat/direct-style-asyncshift-using-loop

### Alternative Solutions

_No response_

### Current Workaround

_No response_

### Additional Context

_No response_
Linked issue comments: user: login: ahoy-jon
created_at: 2025-05-25T20:09:23Z
body: Given the performance difference, a custom `AsyncShift` using `Chunk` and `Foreach` could be worth the effort.

Method | Size | Throughput (ops/s) | Rel. to deferIterable
-- | -- | -- | --
deferIterable | 1024 | 59,225 | 1.00× (baseline)
deferChunk | 1024 | 83,697 | 1.41×
kyoForeach | 1024 | 126,750 | 2.14×
deferIterable | 1048576 | 25.34 | 1.00× (baseline)
deferChunk | 1048576 | 69.90 | 2.76×
kyoForeach | 1048576 | 88.58 | 3.50×
author_association: COLLABORATOR

Diff:
diff --git a/build.sbt b/build.sbt
index 5e3cf9beb..fc99a5734 100644
--- a/build.sbt
+++ b/build.sbt
@@ -606,6 +606,7 @@ lazy val `kyo-bench` =
         .dependsOn(`kyo-core`)
         .dependsOn(`kyo-sttp`)
         .dependsOn(`kyo-stm`)
+        .dependsOn(`kyo-direct`)
         .dependsOn(`kyo-scheduler-zio`)
         .dependsOn(`kyo-scheduler-cats`)
         .disablePlugins(MimaPlugin)
diff --git a/kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala b/kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala
new file mode 100644
index 000000000..efacddfd8
--- /dev/null
+++ b/kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala
@@ -0,0 +1,61 @@
+package kyo.bench
+
+import kyo.*
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.annotations.Param
+import org.openjdk.jmh.infra.Blackhole
+import scala.compiletime.uninitialized
+import scala.util.Random
+
+class ForeachBench extends BaseBench:
+    @Param(Array("1024", "65536", "262144", "1048576"))
+    var size: Int = uninitialized
+
+    var intChunk: Chunk[Int] = uninitialized
+    var intVec: Vector[Int]  = uninitialized
+
+    def intFillFn: Int = Random.nextInt()
+
+    def f(i: Int): Int < Any = i + 1
+
+    @Setup(Level.Trial)
+    def setup(): Unit =
+        intChunk = Chunk.fill(size)(intFillFn)
+        intVec = intChunk.toVector
+    end setup
+
+    @Benchmark def kyoForeach(bh: Blackhole) =
+        val prg = Kyo.foreach(intChunk)(f).map(_.size)
+        bh.consume(prg.eval)
+
+    @Benchmark def deferChunk(bh: Blackhole) =
+        val asVal = intChunk
+        val prg   = defer(asVal.map(i => f(i).now).size)
+        bh.consume(prg.eval)
+    end deferChunk
+
+    @Benchmark def deferSeq(bh: Blackhole) =
+        val asVal: Seq[Int] = intChunk
+        val prg             = defer(asVal.map(i => f(i).now).size)
+        bh.consume(prg.eval)
+    end deferSeq
+
+    @Benchmark def deferVector(bh: Blackhole) =
+        val asVal = intVec
+        val prg   = defer(asVal.map(i => f(i).now).size)
+        bh.consume(prg.eval)
+    end deferVector
+
+    @Benchmark def deferVectorAsSeq(bh: Blackhole) =
+        val asVal: Seq[Int] = intVec
+        val prg             = defer(asVal.map(i => f(i).now).size)
+        bh.consume(prg.eval)
+    end deferVectorAsSeq
+
+    @Benchmark def deferIterable(bh: Blackhole) =
+        val asVal: Iterable[Int] = intChunk
+        val prg                  = defer(asVal.map(i => f(i).now).size)
+        bh.consume(prg.eval)
+    end deferIterable
+
+end ForeachBench
diff --git a/kyo-direct/shared/src/main/scala/kyo/Direct.scala b/kyo-direct/shared/src/main/scala/kyo/Direct.scala
index 820c5205c..52a2d36c1 100644
--- a/kyo-direct/shared/src/main/scala/kyo/Direct.scala
+++ b/kyo-direct/shared/src/main/scala/kyo/Direct.scala
@@ -1,9 +1,7 @@
 package kyo
 
-import cps.CpsMonad
-import cps.CpsMonadContext
 import cps.async
-import directInternal.*
+import internal.*
 import kyo.Ansi.*
 import scala.annotation.tailrec
 import scala.quoted.*
@@ -187,31 +185,13 @@ private def impl[A: Type](body: Expr[A])(using quotes: Quotes): Expr[Any] =
                 }
 
             '{
+
                 given KyoCpsMonad[s] = KyoCpsMonad[s]
+                import kyo.internal.asyncShift.given
+
                 async {
                     ${ transformedBody.asExprOf[A] }
                 }.asInstanceOf[A < s]
             }
     end match
 end impl
-
-object directInternal:
-    given Frame = Frame.internal
-    class KyoCpsMonad[S]
-        extends CpsMonadContext[[A] =>> A < S]
-        with CpsMonad[[A] =>> A < S]
-        with Serializable:
-
-        type Context = KyoCpsMonad[S]
-
-        override def monad: CpsMonad[[A] =>> A < S] = this
-
-        override def apply[A](op: Context => A < S): A < S = op(this)
-
-        override def pure[A](t: A): A < S = t
-
-        override def map[A, B](fa: A < S)(f: A => B): B < S = flatMap(fa)(f)
-
-        override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
-    end KyoCpsMonad
-end directInternal
diff --git a/kyo-direct/shared/src/main/scala/kyo/TestSupport.scala b/kyo-direct/shared/src/main/scala/kyo/TestSupport.scala
index f24fe9838..3172cdfca 100644
--- a/kyo-direct/shared/src/main/scala/kyo/TestSupport.scala
+++ b/kyo-direct/shared/src/main/scala/kyo/TestSupport.scala
@@ -1,10 +1,9 @@
 package kyo
 
-import language.higherKinds
 import scala.language.experimental.macros
 
 object TestSupport:
-    transparent inline def runLiftTest[A, B](inline expected: A)(inline body: B) =
+    transparent inline def runLiftTest[A, B](inline expected: A)(inline body: B): Unit =
         import AllowUnsafe.embrace.danger
         val actual: B = IO.Unsafe.evalOrThrow(defer(body).asInstanceOf[B < IO])
         if !expected.equals(actual) then
diff --git a/kyo-direct/shared/src/main/scala/kyo/internal/AsyncShift.scala b/kyo-direct/shared/src/main/scala/kyo/internal/AsyncShift.scala
new file mode 100644
index 000000000..d781ff0a4
--- /dev/null
+++ b/kyo-direct/shared/src/main/scala/kyo/internal/AsyncShift.scala
@@ -0,0 +1,130 @@
+package kyo.internal
+
+import cps.CpsMonad
+import cps.runtime.IterableOpsAsyncShift
+import kyo.*
+import kyo.kernel.internal.Safepoint
+import scala.annotation.targetName
+import scala.collection.IterableOps
+
+trait asyncShiftLowPriorityImplicit1:
+
+    transparent inline given shiftedIterableOps[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]]]
+        : KyoSeqAsyncShift[A, C, C[A]] = KyoSeqAsyncShift[A, C, C[A]]()
+
+end asyncShiftLowPriorityImplicit1
+
+object asyncShift extends asyncShiftLowPriorityImplicit1:
+
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A] = new ChunkAsyncShift[A]
+
+end asyncShift
+
+class ChunkAsyncShift[A](using Frame) extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]](using Frame)
+    extends IterableOpsAsyncShift[A, C, CA]:
+
+    extension [S, X](chunk: Chunk[X] < S)
+        def resultInto(c: CA): C[X] < S = chunk.map(ch => c.iterableFactory.from(ch))
+    end extension
+
+    extension [S, X, Y](chunks: (Chunk[X], Chunk[Y]) < S)
+        def resultInto(c: CA): (C[X], C[Y]) < S = chunks.map((x, y) => (c.iterableFactory.from(x), c.iterableFactory.from(y)))
+
+    extension [S, K, V](chunks: Map[K, Chunk[V]] < S)
+        @targetName("resultIntoMap")
+        def resultInto(c: CA): Map[K, C[V]] < S = chunks.map(m => m.view.mapValues(ch => c.iterableFactory.from(ch)).toMap)
+
+    override def shiftedFold[F[_], Acc, B, R](
+        c: CA,
+        monad: CpsMonad[F]
+    )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+        monad match
+            case _: KyoCpsMonad[?] =>
+                Kyo.foldLeft(c)(prolog)((state, a) => action(a).map(b => acc(state, a, b))).map(s => epilog(s))
+    end shiftedFold
+
+    override def shiftedStateFold[F[_], S, R](c: CA, monad: CpsMonad[F])(prolog: S, acc: (S, A) => F[S], epilog: S => R): F[R] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.foldLeft(c)(prolog)((s, a) => acc(s, a)).map(s => epilog(s))
+
+    override def shiftedWhile[F[_], S, R](
+        c: CA,
+        monad: CpsMonad[F]
+    )(prolog: S, condition: A => F[Boolean], acc: (S, Boolean, A) => S, epilog: S => R): F[R] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.shiftedWhile(c)(prolog, condition, acc, epilog)
+
+    override def dropWhile[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[C[A]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.dropWhile(c)(a => p(a)).resultInto(c)
+
+    override def filter[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[C[A]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.filter(c)(a => p(a)).resultInto(c)
+
+    override def takeWhile[F[_]](c: CA, monad: CpsMonad[F])(p: (A) => F[Boolean]): F[C[A]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.takeWhile(c)(a => p(a)).resultInto(c)
+
+    override def map[F[_], B](c: CA, monad: CpsMonad[F])(f: A => F[B]): F[C[B]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.foreach(c)(a => f(a)).resultInto(c)
+    end map
+
+    override def flatMap[F[_], B](c: CA, monad: CpsMonad[F])(f: A => F[IterableOnce[B]]): F[C[B]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.foreachConcat(c)(a => f(a)).resultInto(c)
+
+    override def foreach[F[_], U](c: CA, monad: CpsMonad[F])(f: A => F[U]): F[Unit] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.foreachDiscard(c)(a => f(a))
+
+    private def liftPartial[B, S](pf: PartialFunction[A, B < S]): (A => Maybe[B] < S) =
+        a =>
+            pf.lift(a) match
+                case Some(value) => value.map(v => Maybe(v))
+                case None        => Maybe.empty
+
+    override def collect[F[_], B](c: CA, monad: CpsMonad[F])(pf: PartialFunction[A, F[B]]): F[C[B]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.collect(c)(liftPartial(pf)).resultInto(c)
+
+    override def filterNot[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[C[A]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.filter(c)(a => p(a).map(!_)).resultInto(c)
+
+    override def flatten[F[_], B](c: CA, monad: CpsMonad[F])(implicit asIterable: A => F[IterableOnce[B]]): F[C[B]] =
+        monad match
+            case _: KyoCpsMonad[?] => flatMap(c, monad)(asIterable)
+
+    override def groupBy[F[_], K](c: CA, monad: CpsMonad[F])(f: A => F[K]): F[Map[K, C[A]]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.groupBy(c)(f).resultInto(c)
+
+    override def groupMap[F[_], K, B](c: CA, monad: CpsMonad[F])(key: A => F[K])(f: A => F[B]): F[Map[K, C[B]]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.groupMap(c)(key)(f).resultInto(c)
+
+    override def partition[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[(C[A], C[A])] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.partition(c)(p).resultInto(c)
+
+    override def partitionMap[F[_], A1, A2](c: CA, monad: CpsMonad[F])(f: A => F[Either[A1, A2]]): F[(C[A1], C[A2])] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.partitionMap(c)(f).resultInto(c)
+
+    override def scanLeft[F[_], B](c: CA, monad: CpsMonad[F])(z: B)(op: (B, A) => F[B]): F[C[B]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.scanLeft(c)(z)(op).resultInto(c)
+
+    override def span[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[(C[A], C[A])] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.span(c)(p).resultInto(c)
+
+    override def tapEach[F[_], U](c: CA, monad: CpsMonad[F])(f: A => F[U]): F[C[A]] =
+        monad match
+            case _: KyoCpsMonad[?] => Kyo.foreach(c)(x => f(x).andThen(x)).resultInto(c)
+
+end KyoSeqAsyncShift
diff --git a/kyo-direct/shared/src/main/scala/kyo/internal/KyoCpsMonad.scala b/kyo-direct/shared/src/main/scala/kyo/internal/KyoCpsMonad.scala
new file mode 100644
index 000000000..3fc7502db
--- /dev/null
+++ b/kyo-direct/shared/src/main/scala/kyo/internal/KyoCpsMonad.scala
@@ -0,0 +1,27 @@
+package kyo.internal
+
+import cps.CpsMonad
+import cps.CpsMonadContext
+import kyo.*
+import kyo.kernel.internal.Safepoint
+
+final class KyoCpsMonad[S]
+    extends CpsMonadContext[[A] =>> A < S]
+    with CpsMonad[[A] =>> A < S]
+    with Serializable:
+
+    private given Frame = Frame.internal
+
+    type Context = KyoCpsMonad[S]
+
+    override def monad: CpsMonad[[A] =>> A < S] = this
+
+    override def apply[A](op: Context => A < S): A < S = op(this)
+
+    override def pure[A](t: A): A < S = t
+
+    override def map[A, B](fa: A < S)(f: A => B): B < S = fa.flatMap(a => f(a))
+
+    override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
+
+end KyoCpsMonad
diff --git a/kyo-direct/shared/src/main/scala/kyo/Trees.scala b/kyo-direct/shared/src/main/scala/kyo/internal/Trees.scala
similarity index 80%
rename from kyo-direct/shared/src/main/scala/kyo/Trees.scala
rename to kyo-direct/shared/src/main/scala/kyo/internal/Trees.scala
index 0c2c45b2e..026b8d700 100644
--- a/kyo-direct/shared/src/main/scala/kyo/Trees.scala
+++ b/kyo-direct/shared/src/main/scala/kyo/internal/Trees.scala
@@ -1,4 +1,4 @@
-package kyo
+package kyo.internal
 
 import scala.quoted.*
 
@@ -17,9 +17,7 @@ object Trees:
         def goto(using s: Step, q: Quotes)(tree: q.reflect.Tree): Unit = s.addTree(tree)
     end Step
 
-    def traverseGoto(using
-        Quotes
-    )(tree: quotes.reflect.Tree)(pf: Step ?=> PartialFunction[quotes.reflect.Tree, Unit]): Unit =
+    def traverseGoto(using quotes: Quotes)(tree: quotes.reflect.Tree)(pf: Step ?=> PartialFunction[quotes.reflect.Tree, Unit]): Unit =
         import quotes.reflect.*
         (new TreeTraverser:
             override def traverseTree(tree: Tree)(owner: Symbol): Unit =
@@ -30,9 +28,7 @@ object Trees:
         ).traverseTree(tree)(Symbol.spliceOwner)
     end traverseGoto
 
-    def traverse(using
-        Quotes
-    )(tree: quotes.reflect.Tree)(pf: PartialFunction[quotes.reflect.Tree, Unit]): Unit =
+    def traverse(using quotes: Quotes)(tree: quotes.reflect.Tree)(pf: PartialFunction[quotes.reflect.Tree, Unit]): Unit =
         import quotes.reflect.*
         (new TreeTraverser:
             override def traverseTree(tree: Tree)(owner: Symbol): Unit =
@@ -41,7 +37,7 @@ object Trees:
     end traverse
 
     def transform(using
-        Quotes
+        quotes: Quotes
     )(tree: quotes.reflect.Tree)(pf: PartialFunction[quotes.reflect.Tree, quotes.reflect.Term]): quotes.reflect.Tree =
         import quotes.reflect.*
         (new TreeMap:
@@ -50,9 +46,7 @@ object Trees:
         ).transformTree(tree)(Symbol.spliceOwner)
     end transform
 
-    def exists(using
-        Quotes
-    )(tree: quotes.reflect.Tree)(pf: PartialFunction[quotes.reflect.Tree, Boolean]) =
+    def exists(using quotes: Quotes)(tree: quotes.reflect.Tree)(pf: PartialFunction[quotes.reflect.Tree, Boolean]) =
         var r = false
         traverse(tree) {
             case t if pf.isDefinedAt(t) && !r => r = pf(t)
diff --git a/kyo-direct/shared/src/main/scala/kyo/Validate.scala b/kyo-direct/shared/src/main/scala/kyo/internal/Validate.scala
similarity index 97%
rename from kyo-direct/shared/src/main/scala/kyo/Validate.scala
rename to kyo-direct/shared/src/main/scala/kyo/internal/Validate.scala
index 4b9afffbc..5e1cf886b 100644
--- a/kyo-direct/shared/src/main/scala/kyo/Validate.scala
+++ b/kyo-direct/shared/src/main/scala/kyo/internal/Validate.scala
@@ -1,11 +1,9 @@
-package kyo
+package kyo.internal
 
 import kyo.Ansi.*
+import kyo.internal.Trees
 import scala.annotation.tailrec
-import scala.collection.ArrayOps
 import scala.collection.IterableOps
-import scala.collection.MapOps
-import scala.collection.WithFilter
 import scala.quoted.*
 
 private val validMethodNamesForAsyncShift = Set(
@@ -64,11 +62,8 @@ private[kyo] object Validate:
                     qualifier.tpe <:< TypeRepr.of[IterableOps[?, ?, ?]] |
                     qualifier.tpe <:< TypeRepr.of[Option[?]] |
                     qualifier.tpe <:< TypeRepr.of[scala.util.Try[?]] |
-                    qualifier.tpe <:< TypeRepr.of[MapOps[?, ?, ?, ?]] |
-                    qualifier.tpe <:< TypeRepr.of[ArrayOps[?]] |
                     qualifier.tpe <:< TypeRepr.of[Either[?, ?]] |
-                    qualifier.tpe <:< TypeRepr.of[Either.LeftProjection[?, ?]] |
-                    qualifier.tpe <:< TypeRepr.of[WithFilter[?, ?]]
+                    qualifier.tpe <:< TypeRepr.of[Either.LeftProjection[?, ?]]
 
             inline def validName: Boolean = validMethodNamesForAsyncShift.contains(methodName)
 
diff --git a/kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala b/kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala
index 370744c2a..379324583 100644
--- a/kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala
+++ b/kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala
@@ -23,6 +23,7 @@ class ShiftHygieneTest extends Test:
 end ShiftHygieneTest
 
 class ShiftTest extends AnyFreeSpec with Assertions:
+
     "basic" in {
         val x1: Seq[Int < Any] = Seq[Int < Any](1, 2, 3)
         val x2: Seq[Int] < Any = defer:
@@ -57,211 +58,350 @@ class ShiftTest extends AnyFreeSpec with Assertions:
 end ShiftTest
 
 class ShiftMethodSupportTest extends AnyFreeSpec with Assertions:
-    "Iterable" - {
-        val xs: Iterable[Int < Any] = Seq(1, 2, 3, 4)
-        val xsValues: Iterable[Int] = Seq(1, 2, 3, 4)
-        "collectFirst" in {
-            val d: Option[Int] < Any = defer:
-                xs.collectFirst:
-                    case i => i.now
-            d.map(res => assert(res == Option(1)))
-        }
-        "foreach" in {
-            val d: Int < Emit[Int] = defer:
-                xs.foreach(i =>
-                    val v = i.now
-                    Emit.value(v).unit.now
-                )
-                1 // to avoid https://github.com/getkyo/kyo/issues/903
-            Emit.run(d).map((vs, _) => assert(vs == Seq(1, 2, 3, 4)))
-        }
-        "corresponds" in {
-            val d: Boolean < Any = defer:
-                xs.corresponds(xs)((a, b) => a.now == b.now)
 
-            assert(d.eval)
+    def iterableTests[Coll[X] <: Iterable[X] & IterableOps[X, Coll, Coll[X]]](
+        name: String,
+        builder: [X] => Seq[X] => Coll[X]
+    ): Unit =
+        def Coll[X](x: X*): Coll[X] = builder(x)
+
+        name - {
+            def xs: Coll[Int < Any] = Coll(1, 2, 3, 4)
+            def xsValues: Coll[Int] = Coll(1, 2, 3, 4)
+
+            "collectFirst" in {
+                val d: Option[Int] < Any = defer:
+                    xs.collectFirst:
+                        case i => i.now
+                d.map(res => assert(res == Option(1)))
+            }
+            "foreach" in {
+                val d: Unit < Emit[Int] = defer:
+                    xs.foreach(i =>
+                        val v = i.now
+                        Emit.value(v).unit.now
+                    )
+
+                Emit.run(d).map((vs, _) => assert(vs == Seq(1, 2, 3, 4)))
+            }
+            "corresponds" in {
+                val d: Boolean < Any = defer:
+                    xs.corresponds(xs)((a, b) => a.now == b.now)
+
+                assert(d.eval)
+            }
+            "count" in {
+                val d: Int < Any = defer:
+                    xs.count(_.now > 0)
+
+                assert(d.eval == 4)
+            }
+            "exists" in {
+                val d: Boolean < Any = defer:
+                    xs.exists(_.now > 0)
+
+                assert(d.eval)
+            }
+            "forall" in {
+                val d: Boolean < Any = defer:
+                    xs.forall(_.now > 0)
+
+                assert(d.eval)
+            }
+            "find" in {
+                val d: Option[Int] < Any = defer:
+                    xs.find(_.now > 2).map(_.now)
+
+                assert(d.eval == Option(3))
+            }
+            "fold" in {
+                val d: Int < Any = defer:
+
+                    def inc(i: Int): Int < Any = (i + 1).later
+
+                    xsValues.fold[Int](0)((a, b) => inc(a + b).now)
+
+                assert(d.eval == 14)
+            }
+            "foldLeft" in {
+                val d = defer:
+                    xs.foldLeft(0)(_ + _.now)
+
+                assert(d.eval == 10)
+            }
+            "foldRight" in {
+                val d = defer:
+                    xs.foldRight(0)(_.now + _)
+
+                assert(d.eval == 10)
+            }
+            "groupMapReduce" in {
+                val d = defer:
+                    xs.groupMapReduce(_ => 1)(_.now)(_ + _)
+
+                assert(d.eval == Map(1 -> 10))
+            }
+            "maxByOpOption" in {
+                val d: Option[Int] < Any = defer:
+                    xs.maxByOption(_.now).map(_.now)
+
+                assert(d.eval == Option(4))
+            }
+            "map" in {
+                val d: Seq[Int] < Any = defer:
+                    xs.map(_.now).toSeq
+
+                assert(d.eval == Seq(1, 2, 3, 4))
+            }
+            "flatMap" in {
+                val d: Seq[Int] < Any = defer:
+
+                    def f(i: Int) = Seq(1)
+
+                    xs.flatMap(i => f(i.now)).toSeq.distinct
+
+                assert(d.eval == Seq(1))
+            }
+            "collect" in {
+                val d: Seq[Int] < Any = defer:
+                    xs.collect({
+                        case i => i.now
+                    }).toSeq
+
+                assert(d.eval == Seq(1, 2, 3, 4))
+            }
+            "dropWhile" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    Seq(1, 2, 3, 4).dropWhile(f(_).now).toSeq
+
+                assert(d.eval == xsValues.dropWhile(_ < 3).toSeq)
+            }
+            "filter" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.filter(i => f(i).now).toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "filterNot" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.filterNot(i => f(i).now).toSeq
+
+                assert(d.eval == Seq(3, 4))
+            }
+            "groupBy" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.groupBy(i => f(i).now).view.mapValues(_.toSeq).toMap
+
+                assert(d.eval == Map(true -> Seq(1, 2), false -> Seq(3, 4)))
+            }
+            "groupMap" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                // TODO : catch xsValues.groupMap(i => f(i))( ...
+                val d = defer:
+                    xsValues.groupMap(i => f(i).now)(i => f(i).now).view.mapValues(_.toSeq.distinct).toMap
+
+                assert(d.eval == Map(true -> Seq(true), false -> Seq(false)))
+            }
+            "scanLeft" in {
+                val d = defer:
+                    xs.scanLeft(0)((b, e) => b + e.now).toSeq
+
+                assert(d.eval == Seq(0, 1, 3, 6, 10))
+            }
+            "scanRight" in {
+                val d = defer:
+                    xs.scanRight(0)((e, b) => e.now + b).toSeq
+
+                assert(d.eval == Seq(10, 9, 7, 4, 0))
+            }
+            "span" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    val (take, drop) = xsValues.span(i => f(i).now)
+                    take.toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "takeWhile" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.takeWhile(i => f(i).now).toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "partition" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    val (pTrue, _) = xsValues.partition(i => f(i).now)
+                    pTrue.toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "partitionMap" in {
+                def f(i: Int): Either[String, Int] < Any = if i < 3 then
+                    Left(i.toString)
+                else Right(i)
+
+                val d = defer:
+                    val (pLeft, pRight) = xsValues.partitionMap(i => f(i).now)
+                    pLeft.toSeq
+
+                assert(d.eval == Seq("1", "2"))
+            }
+            "tapEach" in {
+                def f(i: Int): Unit < Emit[Int] = Emit.value(i)
+
+                val d: Seq[Int] < Emit[Int] = defer:
+                    xsValues.tapEach(i => f(i).now).toSeq
+
+                val (es, res) = Emit.run(d).eval
+                assert(es == res)
+            }
+            "withFilter" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.withFilter(i => f(i).now).map(x => x).toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+
+            "Choice" - {
+                "map" in {
+                    val result = Choice.run(
+                        defer:
+                            Coll("x", "y").map(str =>
+                                Choice.eval(List(true, false))
+                                    .map(b => if b then str.toUpperCase else str).now
+                            )
+                    ).eval
+
+                    assert(result.contains(Chunk("X", "Y")))
+                    assert(result.contains(Chunk("X", "y")))
+                    assert(result.contains(Chunk("x", "Y")))
+                    assert(result.contains(Chunk("x", "y")))
+                    assert(result.size == 4)
+                }
+
+                "collect" in {
+                    val effects =
+                        Coll("x", "y").map { str =>
+                            Choice.eval(List(true, false)).map(b =>
+                                if b then str.toUpperCase else str
+                            )
+                        }
+                    val result = Choice.run(defer(effects.map(_.now))).eval
+
+                    assert(result.contains(Chunk("X", "Y")))
+                    assert(result.contains(Chunk("X", "y")))
+                    assert(result.contains(Chunk("x", "Y")))
+                    assert(result.contains(Chunk("x", "y")))
+                    assert(result.size == 4)
+                }
+
+                "flatMap" in {
+                    val effects = defer:
+                        val tf = Choice.eval(List(true, false)).later
+                        Coll("x", "y").flatMap(str1 =>
+                            val pred1 = tf.now
+                            Coll("z").map(str2 =>
+                                val pred2 = tf.now
+                                if pred1 & pred2 then
+                                    (str1 + str2).toUpperCase
+                                else if pred1 != pred2 then
+                                    Choice.drop.now
+                                else (str1 + str2)
+                                end if
+                            )
+                        )
+
+                    val result = Choice.run(effects).eval
+                    assert(result.contains(Chunk("XZ", "YZ")))
+                    assert(result.contains(Chunk("XZ", "yz")))
+                    assert(result.contains(Chunk("xz", "YZ")))
+                    assert(result.contains(Chunk("xz", "yz")))
+                    assert(result.size == 4)
+                }
+
+                "foldLeft" in {
+                    val result = Choice.run(
+                        defer:
+                            Coll(1, 1).foldLeft(0)((acc, _) => Choice.eval(List(0, 1)).map(n => acc + n).now)
+                    ).eval
+
+                    assert(result.contains(0))
+                    assert(result.contains(1))
+                    assert(result.contains(2))
+                    assert(result.size == 4)
+                }
+            }
         }
-        "count" in {
-            val d: Int < Any = defer:
-                xs.count(_.now > 0)
+    end iterableTests
 
-            assert(d.eval == 4)
-        }
-        "exists" in {
-            val d: Boolean < Any = defer:
-                xs.exists(_.now > 0)
+    iterableTests[Iterable]("Iterable", [X] => seq => seq)
+    iterableTests[Vector]("Vector", [X] => seq => Vector.from(seq))
+    iterableTests[Chunk]("Chunk", [X] => seq => Chunk.from(seq))
+    iterableTests[List]("List", [X] => seq => List.from(seq))
 
-            assert(d.eval)
-        }
-        "forall" in {
-            val d: Boolean < Any = defer:
-                xs.forall(_.now > 0)
+    // fake Iterable
+    def oneShot[A](a: A*): Iterable[A] =
+        val used = new java.util.concurrent.atomic.AtomicBoolean(false)
+        new Iterable[A]:
+            def iterator: Iterator[A] =
+                if !used.compareAndSet(false, true) then
+                    throw new IllegalStateException("Already consumed!")
+                else Iterator(a*)
 
-            assert(d.eval)
-        }
-        "find" in {
-            val d: Option[Int] < Any = defer:
-                xs.find(_.now > 2).map(_.now)
+        end new
+    end oneShot
 
-            assert(d.eval == Option(3))
-        }
-        "fold" in {
-            val d: Int < Any = defer:
-                def inc(i: Int): Int < Any = (i + 1).later
+    iterableTests[Iterable]("IterableOnce", [X] => seq => oneShot(seq*))
 
-                xsValues.fold[Int](0)((a, b) => inc(a + b).now)
+    "Option" - {
+        val x: Option[Int < Any] = Option(1)
+        val y: Option[Int]       = Option(1)
+        val z: Option[Int] < Any = None
 
-            assert(d.eval == 14)
-        }
-        "foldLeft" in {
-            val d = defer:
-                xs.foldLeft(0)(_ + _.now)
-
-            assert(d.eval == 10)
-        }
-        "foldRight" in {
-            val d = defer:
-                xs.foldRight(0)(_.now + _)
-
-            assert(d.eval == 10)
-        }
-        "groupMapReduce" in {
-            val d = defer:
-                xs.groupMapReduce(_ => 1)(_.now)(_ + _)
-
-            assert(d.eval == Map(1 -> 10))
-        }
-        "maxByOpOption" in {
-            val d: Option[Int] < Any = defer:
-                xs.maxByOption(_.now).map(_.now)
-
-            assert(d.eval == Option(4))
-        }
-        /*"reduceOption" in {
-            val d: Option[Int] < Any = defer:
-                xs.reduceOption(_.now + _.now).map(_.now)
-
-            assert(d.eval == Option(10))
-        }*/
-        "map" in {
-            val d: Seq[Int] < Any = defer:
-                xs.map(_.now).toSeq
-
-            assert(d.eval == Seq(1, 2, 3, 4))
-        }
-        "flatMap" in {
-            val d: Seq[Int] < Any = defer:
-                def f(i: Int) = Seq(1)
-                xs.flatMap(i => f(i.now)).toSeq.distinct
-
-            assert(d.eval == Seq(1))
-        }
-        "collect" in {
-            val d: Seq[Int] < Any = defer:
-                xs.collect({
-                    case i => i.now
-                }).toSeq
-
-            assert(d.eval == Seq(1, 2, 3, 4))
-        }
-        "dropWhile" in {
-            def f(i: Int): Boolean < Any = i < 3
-            val d = defer:
-                xsValues.dropWhile(f(_).now).toSeq
-
-            assert(d.eval == Seq(3, 4))
-        }
         "filter" in {
             def f(i: Int): Boolean < Any = i < 3
             val d = defer:
-                xsValues.filter(i => f(i).now).toSeq
-
-            assert(d.eval == Seq(1, 2))
-        }
-        "filterNot" in {
-            def f(i: Int): Boolean < Any = i < 3
-            val d = defer:
-                xsValues.filterNot(i => f(i).now).toSeq
-
-            assert(d.eval == Seq(3, 4))
-        }
-        "groupBy" in {
-            def f(i: Int): Boolean < Any = i < 3
-            val d = defer:
-                xsValues.groupBy(i => f(i).now).view.mapValues(_.toSeq).toMap
-
-            assert(d.eval == Map(true -> Seq(1, 2), false -> Seq(3, 4)))
-        }
-        "groupMap" in {
-            def f(i: Int): Boolean < Any = i < 3
-
-            // TODO : catch xsValues.groupMap(i => f(i))( ...
-            val d = defer:
-                xsValues.groupMap(i => f(i).now)(i => f(i).now).view.mapValues(_.toSeq.distinct).toMap
-
-            assert(d.eval == Map(true -> Seq(true), false -> Seq(false)))
-        }
-        "scanLeft" in {
-            val d = defer:
-                xs.scanLeft(0)((b, e) => b + e.now).toSeq
-
-            assert(d.eval == Seq(0, 1, 3, 6, 10))
-        }
-        "scanRight" in {
-            val d = defer:
-                xs.scanRight(0)((e, b) => e.now + b).toSeq
-
-            assert(d.eval == Seq(10, 9, 7, 4, 0))
-        }
-        "span" in {
-            def f(i: Int): Boolean < Any = i < 3
-            val d = defer:
-                val (take, drop) = xsValues.span(i => f(i).now)
-                take.toSeq
+                y.filter(i => f(i).now)
 
-            assert(d.eval == Seq(1, 2))
+            assert(d.eval == Option(1))
         }
-        "takeWhile" in {
-            def f(i: Int): Boolean < Any = i < 3
-            val d = defer:
-                xsValues.takeWhile(i => f(i).now).toSeq
 
-            assert(d.eval == Seq(1, 2))
-        }
-        "partition" in {
-            def f(i: Int): Boolean < Any = i < 3
+        "orElse" in {
             val d = defer:
-                val (pTrue, _) = xsValues.partition(i => f(i).now)
-                pTrue.toSeq
+                y.orElse(z.now)
 
-            assert(d.eval == Seq(1, 2))
+            assert(d.eval == Option(1))
         }
-        "partitionMap" in {
-            def f(i: Int): Either[String, Int] < Any = if i < 3 then
-                Left(i.toString)
-            else Right(i)
 
+        "getOrElse" in {
+            val w: Int < Any = 3
             val d = defer:
-                val (pLeft, pRight) = xsValues.partitionMap(i => f(i).now)
-                pLeft.toSeq
+                z.now.getOrElse(w.now)
 
-            assert(d.eval == Seq("1", "2"))
+            assert(d.eval == 3)
         }
-        "tapEach" in {
-            def f(i: Int): Unit < Emit[Int] = Emit.value(i)
 
-            val d: Seq[Int] < Emit[Int] = defer:
-                xsValues.tapEach(i => f(i).now).toSeq
-
-            val (es, res) = Emit.run(d).eval
-            assert(es == res)
-        }
-        "withFilter" in {
-            def f(i: Int): Boolean < Any = i < 3
+        "map" in {
             val d = defer:
-                xsValues.withFilter(i => f(i).now).map(x => x).toSeq
+                x.map(_.now + 1)
 
-            assert(d.eval == Seq(1, 2))
+            assert(d.eval == Option(2))
         }
     }
 
diff --git a/kyo-kernel/shared/src/main/scala/kyo/Kyo.scala b/kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
index bcadea1c3..c3fe0686a 100644
--- a/kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
+++ b/kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
@@ -173,6 +173,41 @@ object Kyo:
                             else f(indexedSeq(idx)).map(u => Loop.continue(acc.append(u)))
     end foreach
 
+    /** Applies an effect-producing function to each element of an `IterableOnce`, and concatenates the resulting collections.
+      *
+      * @param source
+      *   The input `IterableOnce`
+      * @param f
+      *   The effect-producing function that returns a collection of results per element
+      * @return
+      *   A new effect that produces a flattened Chunk of all results
+      */
+    def foreachConcat[A, B, S](source: IterableOnce[A])(f: Safepoint ?=> A => IterableOnce[B] < S)(using Frame, Safepoint): Chunk[B] < S =
+        source.knownSize match
+            case 0 => Chunk.empty
+            case 1 =>
+                val head = source.iterator.next()
+                f(head).map(bs => Chunk.from(bs))
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk.empty[B]): (seq, acc) =>
+                            if seq.isEmpty then Loop.done(acc)
+                            else
+                                f(seq.head).map(bs =>
+                                    Loop.continue(seq.tail, acc ++ bs)
+                                )
+                    case other =>
+                        val indexedSeq = toIndexed(other)
+                        val size       = indexedSeq.length
+                        Loop.indexed(Chunk.empty[B]): (idx, acc) =>
+                            if idx == size then Loop.done(acc)
+                            else
+                                f(indexedSeq(idx)).map(bs =>
+                                    Loop.continue(acc ++ bs)
+                                )
+    end foreachConcat
+
     /** Applies an effect-producing function to each element of a sequence along with its index.
       *
       * @param source
@@ -454,6 +489,47 @@ object Kyo:
                                     case false => Loop.done(acc)
     end takeWhile
 
+    /** Splits the collection into prefix/suffix pair where all elements in the prefix satisfy `f`.
+      *
+      * @param f
+      *   A predicate that returns `true` while elements should be included in the prefix
+      * @return
+      *   A tuple `(prefix, suffix)` where:
+      *   - `prefix`: All elements before first failure of `f`
+      *   - `suffix`: First failing element and all remaining elements
+      * @note
+      *   Optimized for both linear and indexed sequences
+      */
+    def span[A, S](source: IterableOnce[A])(f: Safepoint ?=> A => Boolean < S)(using Frame, Safepoint): (Chunk[A], Chunk[A]) < S =
+        source.knownSize match
+            case 0 => (Chunk.empty, Chunk.empty)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(Chunk.empty[A], linearSeq): (acc, seq) =>
+                            if seq.isEmpty then
+                                Loop.done((acc, Chunk.empty))
+                            else
+                                val curr = seq.head
+                                f(curr).map:
+                                    case true  => Loop.continue(acc.append(curr), seq.tail)
+                                    case false => Loop.done((acc, Chunk.from(seq)))
+
+                    case other =>
+                        val indexedSeq = toIndexed(other)
+                        val size       = indexedSeq.length
+                        Loop.indexed: idx =>
+                            if idx == size then
+                                Loop.done((Chunk.from(indexedSeq), Chunk.empty))
+                            else
+                                val curr = indexedSeq(idx)
+                                f(curr).map({
+                                    case true => Loop.continue
+                                    case false =>
+                                        Loop.done(Chunk.from(indexedSeq).splitAt(idx))
+                                })
+    end span
+
     /** Drops elements from an `IterableOnce` while a predicate holds true.
       *
       * @param source
@@ -475,7 +551,7 @@ object Kyo:
                                 val curr = seq.head
                                 f(curr).map:
                                     case true  => Loop.continue(seq.tail)
-                                    case false => Loop.done(Chunk.from(seq.tail))
+                                    case false => Loop.done(Chunk.from(seq))
                     case other =>
                         val indexedSeq = toIndexed(other)
                         val size       = indexedSeq.length
@@ -489,6 +565,178 @@ object Kyo:
                 end match
     end dropWhile
 
+    def partition[S, A](source: IterableOnce[A])(f: Safepoint ?=> A => Boolean < S)(using
+        Frame,
+        Safepoint
+    ): (Chunk[A], Chunk[A]) < S =
+        source.knownSize match
+            case 0 => (Chunk.empty, Chunk.empty)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk.empty[A], Chunk.empty[A]) { (seq, trues, falses) =>
+                            if seq.isEmpty then Loop.done((trues, falses))
+                            else
+                                f(seq.head).map { matches =>
+                                    if matches then
+                                        Loop.continue(seq.tail, trues.append(seq.head), falses)
+                                    else
+                                        Loop.continue(seq.tail, trues, falses.append(seq.head))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk.empty[A], Chunk.empty[A]) { (idx, trues, falses) =>
+                            if idx == arr.length then
+                                Loop.done((trues, falses))
+                            else
+                                f(arr(idx)).map { matches =>
+                                    if matches then
+                                        Loop.continue(trues.append(arr(idx)), falses)
+                                    else
+                                        Loop.continue(trues, falses.append(arr(idx)))
+                                }
+                        }
+
+    def partitionMap[S, A, A1, A2](source: IterableOnce[A])(f: Safepoint ?=> A => Either[A1, A2] < S)(using
+        Frame,
+        Safepoint
+    ): (Chunk[A1], Chunk[A2]) < S =
+        source.knownSize match
+            case 0 => (Chunk.empty, Chunk.empty)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk.empty[A1], Chunk.empty[A2]) { (seq, lefts, rights) =>
+                            if seq.isEmpty then Loop.done((lefts, rights))
+                            else
+                                f(seq.head).map {
+                                    case Left(a1)  => Loop.continue(seq.tail, lefts.append(a1), rights)
+                                    case Right(a2) => Loop.continue(seq.tail, lefts, rights.append(a2))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk.empty[A1], Chunk.empty[A2]) { (idx, lefts, rights) =>
+                            if idx >= arr.length then
+                                Loop.done((lefts, rights))
+                            else
+                                f(arr(idx)).map {
+                                    case Left(a1)  => Loop.continue(lefts.append(a1), rights)
+                                    case Right(a2) => Loop.continue(lefts, rights.append(a2))
+                                }
+                        }
+
+    /** Computes a prefix scan of the collection.
+      *
+      * @param z
+      *   Initial accumulator value
+      * @param op
+      *   Effectful operation that combines accumulator with each element
+      * @return
+      *   Chunk containing all intermediate accumulator states
+      */
+    def scanLeft[S, A, B](source: IterableOnce[A])(z: B)(op: Safepoint ?=> (B, A) => B < S)(using
+        Frame,
+        Safepoint
+    ): Chunk[B] < S =
+        source.knownSize match
+            case 0 => Chunk(z)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk(z), z) { (seq, acc, current) =>
+                            if seq.isEmpty then Loop.done(acc)
+                            else
+                                op(current, seq.head).map { next =>
+                                    Loop.continue(seq.tail, acc.append(next), next)
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk(z), z) { (idx, acc, current) =>
+                            if idx == arr.length then Loop.done(acc)
+                            else
+                                op(current, arr(idx)).map { next =>
+                                    Loop.continue(acc.append(next), next)
+                                }
+                        }
+
+    def groupBy[S, A, K](source: IterableOnce[A])(f: Safepoint ?=> A => K < S)(using
+        Frame,
+        Safepoint
+    ): Map[K, Chunk[A]] < S =
+        source.knownSize match
+            case 0 => Map.empty[K, Chunk[A]]
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Map.empty[K, Chunk[A]]) { (seq, acc) =>
+                            if seq.isEmpty then
+                                Loop.done(acc)
+                            else
+                                val a = seq.head
+                                f(a).map { k =>
+                                    val current = acc.getOrElse(k, Chunk.empty)
+                                    Loop.continue(seq.tail, acc.updated(k, current :+ a))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Map.empty[K, Chunk[A]]) { (idx, acc) =>
+                            if idx == arr.length then
+                                Loop.done(acc)
+                            else
+                                val a = arr(idx)
+                                f(a).map { k =>
+                                    val current = acc.getOrElse(k, Chunk.empty)
+                                    Loop.continue(acc.updated(k, current :+ a))
+                                }
+                        }
+
+    def groupMap[S, A, K, B](source: IterableOnce[A])(key: Safepoint ?=> A => K < S)(f: Safepoint ?=> A => B < S)(using
+        Frame,
+        Safepoint
+    ): Map[K, Chunk[B]] < S =
+        source.knownSize match
+            case 0 => Map.empty[K, Chunk[B]]
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Map.empty[K, Chunk[B]]) { (seq, acc) =>
+                            if seq.isEmpty then
+                                Loop.done(acc)
+                            else
+                                val a = seq.head
+                                for
+                                    k <- key(a)
+                                    b <- f(a)
+                                    current = acc.getOrElse(k, Chunk.empty)
+                                    updated = acc.updated(k, current :+ b)
+                                yield Loop.continue(seq.tail, updated)
+                                end for
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Map.empty[K, Chunk[B]]) { (idx, acc) =>
+                            if idx >= arr.length then
+                                Loop.done(acc)
+                            else
+                                val a = arr(idx)
+                                for
+                                    k <- key(a)
+                                    b <- f(a)
+                                    current = acc.getOrElse(k, Chunk.empty)
+                                    updated = acc.updated(k, current :+ b)
+                                yield Loop.continue(updated)
+                                end for
+                        }
+
     /** Creates a Chunk by repeating an effect-producing value.
       *
       * @param n
@@ -508,4 +756,37 @@ object Kyo:
         source match
             case seq: IndexedSeq[A] => seq
             case other              => Chunk.from(other)
+
+    // for kyo-direct
+    private[kyo] def shiftedWhile[A, S, B, C](source: IterableOnce[A])(
+        prolog: B,
+        f: Safepoint ?=> A => Boolean < S,
+        acc: (B, Boolean, A) => B,
+        epilog: B => C
+    )(using Frame, Safepoint): C < S =
+        source.knownSize match
+            case 0 => epilog(prolog)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] => Loop(linearSeq, prolog): (seq, b) =>
+                            if seq.isEmpty then Loop.done(epilog(b))
+                            else
+                                val curr = seq.head
+                                f(curr).map:
+                                    case true  => Loop.continue(seq.tail, acc(b, true, curr))
+                                    case false => Loop.done(epilog(acc(b, false, curr)))
+
+                    case other =>
+                        val indexedSeq = toIndexed(other)
+                        val size       = indexedSeq.length
+                        Loop.indexed(prolog): (idx, b) =>
+                            if idx == size then Loop.done(epilog(b))
+                            else
+                                val curr = indexedSeq(idx)
+                                f(curr).map:
+                                    case true  => Loop.continue(acc(b, true, curr))
+                                    case false => Loop.done(epilog(acc(b, false, curr)))
+
+        end match
+    end shiftedWhile
 end Kyo
diff --git a/kyo-kernel/shared/src/test/scala/kyo/KyoTest.scala b/kyo-kernel/shared/src/test/scala/kyo/KyoTest.scala
index 635670c9f..dbde80f8d 100644
--- a/kyo-kernel/shared/src/test/scala/kyo/KyoTest.scala
+++ b/kyo-kernel/shared/src/test/scala/kyo/KyoTest.scala
@@ -247,6 +247,90 @@ class KyoTest extends Test:
             assert(TestEffect1.run(Kyo.fill(100)(TestEffect1(1))).map(_.size).eval == 100)
         }
 
+        "dropWhile" in {
+            def f(i: Int): Boolean < Any = true
+
+            def isEven(i: Int): Boolean < Any = i % 2 == 0
+
+            def lessThanThree(i: Int): Boolean < Any = i < 3
+
+            assert(Kyo.dropWhile(Seq.empty[Int])(a => f(a)).eval == Seq.empty)
+
+            assert(Kyo.dropWhile(Chunk(1, 2, 3, 4))(x => x < 3).eval == Chunk(3, 4))
+            assert(Kyo.dropWhile(List(1, 2, 3, 4))(x => x < 3).eval == Chunk(3, 4))
+
+            assert(Kyo.dropWhile(Seq.empty[Int])(x => f(x)).eval == Seq.empty)
+            assert(Kyo.dropWhile(Chunk(1, 2, 3))(x => f(x)).eval == Seq.empty)
+            assert(Kyo.dropWhile(Chunk(2, 4, 5, 6))(x => isEven(x)).eval == Chunk(5, 6))
+            assert(Kyo.dropWhile(Chunk(1, 2, 3, 4))(x => lessThanThree(x)).eval == Chunk(3, 4))
+            assert(Kyo.dropWhile(Chunk(2, 4, 5))(x => isEven(x)).eval == Chunk(5))
+            assert(Kyo.dropWhile(Chunk(1, 2, -1, 3))(x => lessThanThree(x)).eval == Chunk(3))
+            assert(Kyo.dropWhile(Chunk(4, 1, 2))(x => lessThanThree(x)).eval == Chunk(4, 1, 2))
+        }
+
+        "takeWhile" in {
+            def f(i: Int): Boolean < Any             = true
+            def isEven(i: Int): Boolean < Any        = i % 2 == 0
+            def lessThanThree(i: Int): Boolean < Any = i < 3
+
+            assert(Kyo.takeWhile(Seq.empty[Int])(a => f(a)).eval == Seq.empty)
+            assert(Kyo.takeWhile(Chunk.fill(1)(2))(a => f(a).map(!_)).eval == Seq.empty)
+
+            assert(Kyo.takeWhile(Seq.empty[Int])(x => f(x)).eval == Seq.empty)
+            assert(Kyo.takeWhile(Chunk(1, 2, 3))(x => f(x)).eval == Chunk(1, 2, 3))
+            assert(Kyo.takeWhile(Chunk(2, 4, 5, 6))(x => isEven(x)).eval == Chunk(2, 4))
+            assert(Kyo.takeWhile(Chunk(1, 2, 3, 4))(x => lessThanThree(x)).eval == Chunk(1, 2))
+            assert(Kyo.takeWhile(Chunk(2, 4, 5))(x => isEven(x)).eval == Chunk(2, 4))
+            assert(Kyo.takeWhile(Chunk(1, 2, -1, 3))(x => lessThanThree(x)).eval == Chunk(1, 2, -1))
+            assert(Kyo.takeWhile(Chunk(4, 1, 2))(x => lessThanThree(x)).eval == Seq.empty)
+        }
+
+        "shiftedWhile" in {
+            def isEven(i: Int): Boolean < Any = i % 2 == 0
+
+            def lessThanThree(i: Int): Boolean < Any = i < 3
+
+            val countWhile = Kyo.shiftedWhile(Chunk(1, 2, 3, 4, 5))(
+                prolog = 0,
+                f = lessThanThree,
+                acc = (count, include, _) => count + 1,
+                epilog = identity
+            )
+            assert(countWhile.eval == 3)
+
+            val sumWithMessage = Kyo.shiftedWhile(Chunk(1, 2, -1, 3, 4))(
+                prolog = 0,
+                f = lessThanThree,
+                acc = (sum, include, curr) => if include then sum + curr else sum,
+                epilog = sum => s"Sum: $sum"
+            )
+            assert(sumWithMessage.eval == "Sum: 2") // 1 + 2 + (-1) = 2
+
+            val collectUntilOdd = Kyo.shiftedWhile(Chunk(2, 4, 6, 7, 8))(
+                prolog = List.empty,
+                f = isEven,
+                acc = (list, include, curr) => if include then curr :: list else list,
+                epilog = _.reverse // Maintain original order
+            )
+            assert(collectUntilOdd.eval == List(2, 4, 6))
+
+            val emptyTest = Kyo.shiftedWhile(Seq.empty[Int])(
+                prolog = "Default",
+                f = _ => true,
+                acc = (str, _, curr) => str + curr.toString,
+                epilog = _.toUpperCase
+            )
+            assert(emptyTest.eval == "DEFAULT")
+
+            val arrayTest = Kyo.shiftedWhile(Array(1, 2, 3, 4))(
+                prolog = "",
+                f = lessThanThree,
+                acc = (str, include, curr) => if include then str + curr.toString else str,
+                epilog = _.length
+            )
+            assert(arrayTest.eval == 2)
+        }
+
         "stack safety" - {
             val n = 1000
 


Reviews:
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-26T18:02:09Z
user: login: fwbrasil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T01:19:04Z
user: login: fwbrasil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T01:21:01Z
user: login: fwbrasil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T01:23:46Z
user: login: fwbrasil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T01:29:22Z
user: login: fwbrasil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T01:33:21Z
user: login: fwbrasil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T01:36:30Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T09:42:58Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T09:45:07Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T10:23:52Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T10:31:53Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T12:00:49Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T12:13:02Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T13:18:33Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T14:29:05Z
user: login: fwbrasil
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-27T14:46:23Z
user: login: hearnadam
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T17:31:13Z
user: login: hearnadam
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T17:34:16Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T22:23:53Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-28T22:24:18Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:05:14Z
user: login: hearnadam
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:17:48Z
user: login: hearnadam
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:18:38Z
user: login: hearnadam
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:20:20Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:25:33Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:27:23Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T00:29:53Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T13:14:16Z
user: login: hearnadam
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T17:44:38Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T17:54:40Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T18:07:46Z
user: login: hearnadam
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-29T23:59:43Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-30T00:13:19Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-30T00:16:12Z
user: login: ahoy-jon
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-30T00:22:50Z
user: login: hearnadam
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-30T00:32:35Z

Review comments:
diff_hunk: @@ -454,6 +454,41 @@ object Kyo:
                                     case false => Loop.done(acc)
     end takeWhile
 
+    /** from Dotty-Cps-Async override def shiftedWhile[F[_], S, R]( c: CA, monad: CpsMonad[F] )(prolog: S, condition: A => F[Boolean], acc:
+      * (S, Boolean, A) => S, epilog: S => R): F[R]
+      */
+    private[kyo] def shiftedWhile[A, S, B, C](source: IterableOnce[A])(
+        prolog: B,
+        f: Safepoint ?=> A => Boolean < S,
+        acc: (B, Boolean, A) => B,
+        epilog: B => C
+    )(using Frame, Safepoint): C < S =
path: kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
user: login: ahoy-jon
body: it needs more test for this part
created_at: 2025-05-26T18:02:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package kyo.bench
+
+import kyo.*
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.annotations.Param
+import org.openjdk.jmh.infra.Blackhole
+import scala.compiletime.uninitialized
+import scala.util.Random
+
+class ForeachBench extends BaseBench:
+    @Param(Array("1024", "65536", "262144", "1048576"))
+    var size: Int = uninitialized
+
+    var intChunk: Chunk[Int] = uninitialized
+    var intVec: Vector[Int]  = uninitialized
+
+    def intFillFn: Int = Random.nextInt()
+
+    def f(i: Int): Int < Any = i + 1
+
+    @Setup(Level.Trial)
+    def setup(): Unit =
+        intChunk = Chunk.fill(size)(intFillFn)
+        intVec = intChunk.toVector
+    end setup
+
+    @Benchmark def kyoForeach(bh: Blackhole) =
+        val prg = Kyo.foreach(intChunk)(f).map(_.size)
+        bh.consume(prg.eval)
path: kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala
user: login: fwbrasil
body: The return values of benchmark methods are automatically consumed by a blackhole. There's no need for the explicit `Blackhole` use
created_at: 2025-05-27T01:19:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package kyo.bench
+
+import kyo.*
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.annotations.Param
+import org.openjdk.jmh.infra.Blackhole
+import scala.compiletime.uninitialized
+import scala.util.Random
+
+class ForeachBench extends BaseBench:
+    @Param(Array("1024", "65536", "262144", "1048576"))
+    var size: Int = uninitialized
+
+    var intChunk: Chunk[Int] = uninitialized
+    var intVec: Vector[Int]  = uninitialized
+
+    def intFillFn: Int = Random.nextInt()
+
+    def f(i: Int): Int < Any = i + 1
+
+    @Setup(Level.Trial)
+    def setup(): Unit =
path: kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala
user: login: fwbrasil
body: I think we can initialize in the class body directly and avoid `var`
created_at: 2025-05-27T01:21:01Z
author_association: COLLABORATOR
diff_hunk: @@ -454,6 +454,41 @@ object Kyo:
                                     case false => Loop.done(acc)
     end takeWhile
 
+    /** from Dotty-Cps-Async override def shiftedWhile[F[_], S, R]( c: CA, monad: CpsMonad[F] )(prolog: S, condition: A => F[Boolean], acc:
path: kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
user: login: fwbrasil
body: can you move this to `kyo-direct`?  It seems a more specific need
created_at: 2025-05-27T01:23:46Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
+        end shiftedFold
+
+        override def shiftedStateFold[F[_], S, R](c: CA, monad: CpsMonad[F])(prolog: S, acc: (S, A) => F[S], epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) => Kyo.foldLeft(c)(prolog)((s, a) => acc(s, a)).map(s => epilog(s))
+                case _                               => super.shiftedStateFold(c, monad)(prolog, acc, epilog)
+
+        override def shiftedWhile[F[_], S, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: S, condition: A => F[Boolean], acc: (S, Boolean, A) => S, epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) => Kyo.shiftedWhile(c)(prolog, condition, acc, epilog)
+                case _                               => super.shiftedWhile(c, monad)(prolog, condition, acc, epilog)
+
+        override def map[F[_], B](c: CA, monad: CpsMonad[F])(f: A => F[B]): F[C[B]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreach(c)(a => f(a))
+                case _                 => super.map(c, monad)(f)
+
+        override def foreach[F[_], U](c: CA, monad: CpsMonad[F])(f: A => F[U]): F[Unit] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreachDiscard(c)(a => f(a))
+                case _                 => super.foreach(c, monad)(f)
+    end KyoSeqAsyncShift
+
+    class SeqToChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Seq, Seq[A]]
+    class ChunkAsyncShift[A]      extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+    transparent inline given shiftedSeqToChunk[A]: SeqToChunkAsyncShift[A] = new SeqToChunkAsyncShift[A]
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A]           = new ChunkAsyncShift[A]
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: fwbrasil
body: is `transparent` required?
created_at: 2025-05-27T01:29:22Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: fwbrasil
body: It seems the default implementation uses [mutable iterators](https://github.com/dotty-cps-async/dotty-cps-async/blob/b2233ff122bd1b06e83b0839f79dc73cbe5826fd/shared/src/main/scala/cps/runtime/IterableAsyncShift.scala#L17) across transformations. It's problematic with Kyo's effects since some rely on multi-shot continuations. Can we always use the `Kyo.*` collection methods and not allow use of the default implementation?
created_at: 2025-05-27T01:33:20Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: fwbrasil
body: See https://github.com/getkyo/kyo/pull/1027 for more background on the issue with mutable iterators and effects that use multi-shot continuations.
created_at: 2025-05-27T01:36:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package kyo.bench
+
+import kyo.*
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.annotations.Param
+import org.openjdk.jmh.infra.Blackhole
+import scala.compiletime.uninitialized
+import scala.util.Random
+
+class ForeachBench extends BaseBench:
+    @Param(Array("1024", "65536", "262144", "1048576"))
+    var size: Int = uninitialized
+
+    var intChunk: Chunk[Int] = uninitialized
+    var intVec: Vector[Int]  = uninitialized
+
+    def intFillFn: Int = Random.nextInt()
+
+    def f(i: Int): Int < Any = i + 1
+
+    @Setup(Level.Trial)
+    def setup(): Unit =
+        intChunk = Chunk.fill(size)(intFillFn)
+        intVec = intChunk.toVector
+    end setup
+
+    @Benchmark def kyoForeach(bh: Blackhole) =
+        val prg = Kyo.foreach(intChunk)(f).map(_.size)
+        bh.consume(prg.eval)
path: kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala
user: login: ahoy-jon
body: I don't wanted to account for prg, which could be more costly for small size collection.
created_at: 2025-05-27T09:42:58Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
+        end shiftedFold
+
+        override def shiftedStateFold[F[_], S, R](c: CA, monad: CpsMonad[F])(prolog: S, acc: (S, A) => F[S], epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) => Kyo.foldLeft(c)(prolog)((s, a) => acc(s, a)).map(s => epilog(s))
+                case _                               => super.shiftedStateFold(c, monad)(prolog, acc, epilog)
+
+        override def shiftedWhile[F[_], S, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: S, condition: A => F[Boolean], acc: (S, Boolean, A) => S, epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) => Kyo.shiftedWhile(c)(prolog, condition, acc, epilog)
+                case _                               => super.shiftedWhile(c, monad)(prolog, condition, acc, epilog)
+
+        override def map[F[_], B](c: CA, monad: CpsMonad[F])(f: A => F[B]): F[C[B]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreach(c)(a => f(a))
+                case _                 => super.map(c, monad)(f)
+
+        override def foreach[F[_], U](c: CA, monad: CpsMonad[F])(f: A => F[U]): F[Unit] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreachDiscard(c)(a => f(a))
+                case _                 => super.foreach(c, monad)(f)
+    end KyoSeqAsyncShift
+
+    class SeqToChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Seq, Seq[A]]
+    class ChunkAsyncShift[A]      extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+    transparent inline given shiftedSeqToChunk[A]: SeqToChunkAsyncShift[A] = new SeqToChunkAsyncShift[A]
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A]           = new ChunkAsyncShift[A]
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: ahoy-jon
body: Not sure, but that's what is done in `dotty-cps-async`: https://github.com/dotty-cps-async/dotty-cps-async/blob/master/shared/src/main/scala/cps/AsyncShift.scala
created_at: 2025-05-27T09:45:07Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: ahoy-jon
body: Is there a way to test it? by default (without this PR), `dotty-cps-async` will use the mutable iterator (for all iteration), but chain using `KyoCpsMonad`.

I will try to reproduce this issue with something like:
```scala
import java.util.concurrent.atomic.AtomicBoolean

val oneShot: IterableOnce[Int] = {
  val used = new AtomicBoolean(false)
  new IterableOnce[Int] {
    def iterator: Iterator[Int] = {
      if (!used.compareAndSet(false, true))
        throw new IllegalStateException("Already consumed!")
      Iterator(1, 2, 3)
    }
  }
}
```
---
Thinking about it, I think the problem could still exist when using `Kyo.*` is `Kyo.foreach(oneShot)(...)` is replayed for some reason, with the Iterable defined before and `IO & Abort` block containing a call to `Kyo.foreach`, with a retry:

```scala
class MutableIterator extends Test:
    def oneShot(): IterableOnce[Int] =
        val used = new java.util.concurrent.atomic.AtomicBoolean(false)
        new IterableOnce[Int]:
            def iterator: Iterator[Int] =
                if !used.compareAndSet(false, true) then
                    throw new IllegalStateException("Already consumed!")
                else Iterator(1, 2, 3)

        end new
    end oneShot

    "recall" in run {
        val it: IterableOnce[Int] = oneShot()

        def process(i: Int): Int < (IO & Abort[String]) =
            Console.printLine(s"processing $i").andThen(
                if i == 2 then Abort.fail("abort on 2")
                else i
            )

        val prg: kyo.Chunk[Int] < (kyo.Async & kyo.Abort[String]) = Retry(Schedule.repeat(2))(Kyo.foreach(it)(i => process(i)))

        Abort.run(prg).map(x => assert(x == Result.fail("abort on 2")))

    }
end MutableIterator
```
which execute to:
```
processing 1
processing 2
 - recall *** FAILED *** (25 milliseconds)
  Panic(java.lang.IllegalStateException: Already consumed!) did not equal Failure("abort on 2") (ShiftTest.scala:372)
  Analysis:
  Result$package$Result$Panic(exception: java.lang.IllegalStateException: Already consumed! -> , failure: -> abort on 2)
```





created_at: 2025-05-27T10:23:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package kyo.bench
+
+import kyo.*
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.annotations.Param
+import org.openjdk.jmh.infra.Blackhole
+import scala.compiletime.uninitialized
+import scala.util.Random
+
+class ForeachBench extends BaseBench:
+    @Param(Array("1024", "65536", "262144", "1048576"))
+    var size: Int = uninitialized
+
+    var intChunk: Chunk[Int] = uninitialized
+    var intVec: Vector[Int]  = uninitialized
+
+    def intFillFn: Int = Random.nextInt()
+
+    def f(i: Int): Int < Any = i + 1
+
+    @Setup(Level.Trial)
+    def setup(): Unit =
path: kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala
user: login: ahoy-jon
body: Sorry, I don't see how when using JMH with different Param size.
created_at: 2025-05-27T10:31:53Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: ahoy-jon
body: Not sure about it, I would not accept `IterableOnce[?]` as an input for `Kyo.*` ...

That will be a good use case for rules in Scalafix, to check if a composition can lead to problems (use of multi-shot + impure construct in the same scope).
created_at: 2025-05-27T12:00:49Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: ahoy-jon
body: > It seems the default implementation uses [mutable iterators](https://github.com/dotty-cps-async/dotty-cps-async/blob/b2233ff122bd1b06e83b0839f79dc73cbe5826fd/shared/src/main/scala/cps/runtime/IterableAsyncShift.scala#L17) across transformations. It's problematic with Kyo's effects since some rely on multi-shot continuations. Can we always use the `Kyo.*` collection methods and not allow use of the default implementation?

Going back to your initial feedback:
* by default, for any `Iterable[?]`, `dotty-cps-async` will use `KyoCpsMonad` with mutable iterators. 
* ~I don't think the way it's done for a `defer(it.map(i => f(i).now))` is less safe compare to `Kyo.foreach(it)(i => f(i))`. (I cannot see the example that would produce a different result)~
* the current PR provide for `Seq[?]` and `Chunk[?]` (only, using `Chunk[?]` as the output) a optimized implementation for :
  * `map` (using `Kyo.foreach`)
  * `foreach` (using `Kyo.foreachDiscard`)
  * and if the collection `size` is greater than `65536` elements, a call to `Loop` through building blocks `shiftedFold`, ...
  
  
What can be done:
* remove the guard for `65536` elements for `Seq[?]` and `Chunk[?]` on all operations
* provide something for `Iterable[?]` (using `Chunk[?]` as the output)
* provide something for most collection if https://github.com/getkyo/kyo/issues/1020 is fixed

---
edit: I was wrong!

created_at: 2025-05-27T12:13:02Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: ahoy-jon
body: added a test here for `Choice`:
https://github.com/getkyo/kyo/pull/1212/files#diff-532b921c8fe59a95c2cfc5be06126acd8bc43486c60d06eac0ea5859d69aaf32R347-R364
created_at: 2025-05-27T13:18:33Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +220,49 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+        private inline def useLoop(c: CA): Boolean = c.knownSize > 0xffff || c.knownSize == -1
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] if useLoop(c) =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+                case _ => super.shiftedFold(c, monad)(prolog, action, acc, epilog)
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: ahoy-jon
body: Conclusion, each call to: `c.iterableFactory.newBuilder` https://github.com/dotty-cps-async/dotty-cps-async/blob/a1c8baf796d4a002aa17eef53c3d9a8e65f04f48/shared/src/main/scala/cps/runtime/IterableAsyncShift.scala#L218 creates a issue when using `Choice`.

The guard as been removed, so there is no issues with `Chunk[?]` and `Seq[?]` after this PR, but `Vector[?]` is still an issue.
```
[info] - foreach - Vector *** FAILED *** (11 milliseconds)
[info]   Chunk.Indexed(Vector("X", "Y"), Vector("X", "Y", "y"), Vector("X", "Y", "y", "x")) did not contain Chunk.Indexed("X", "y") (ShiftTest.scala:454)
```

created_at: 2025-05-27T14:29:04Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,61 @@
+package kyo.bench
+
+import kyo.*
+import org.openjdk.jmh.annotations.*
+import org.openjdk.jmh.annotations.Param
+import org.openjdk.jmh.infra.Blackhole
+import scala.compiletime.uninitialized
+import scala.util.Random
+
+class ForeachBench extends BaseBench:
+    @Param(Array("1024", "65536", "262144", "1048576"))
+    var size: Int = uninitialized
+
+    var intChunk: Chunk[Int] = uninitialized
+    var intVec: Vector[Int]  = uninitialized
+
+    def intFillFn: Int = Random.nextInt()
+
+    def f(i: Int): Int < Any = i + 1
+
+    @Setup(Level.Trial)
+    def setup(): Unit =
path: kyo-bench/src/main/scala/kyo/bench/ForeachBench.scala
user: login: fwbrasil
body: ah, I hadn't noticed it! makes sense
created_at: 2025-05-27T14:46:23Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +223,96 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] =>
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: hearnadam
body: Perhaps I am missing something, but when will this not match?
created_at: 2025-05-28T17:31:13Z
author_association: COLLABORATOR
diff_hunk: @@ -297,4 +297,230 @@ class ShiftMethodSupportTest extends AnyFreeSpec with Assertions:
             assert(d.eval == 2)
         }
     }
+
+    "Chunk" - {
+        "map < Any" in {
+            val xs = Chunk(1, 2, 3)
+
+            def f(i: Int): Int < Any = i + 1
+            val d = defer:
+                xs.map(i => f(i).now)
+
+            assert(d.eval == Chunk(2, 3, 4))
+        }
+        "map < Abort" in {
+            val xs = Chunk(1, 2, 3)
+            def f(i: Int): Int < Abort[String] =
+                if i == 2 then Abort.fail("2") else i
+
+            val d = defer:
+                xs.map(i => f(i).now)
+
+            assert(Abort.run(d).eval == Result.fail("2"))
+        }
+
+        val xs = Chunk.fill(n = 0xffff + 1)(elem = 2)
+        "find" in {
+            def f(i: Int): Boolean < Any = i == 2
+            val d = defer:
+                xs.find(i => f(i).now)
+
+            assert(d.eval == Option(2))
+        }
+        "count" in {
+            def f(i: Int): Boolean < Any = i > 0
+            val d: Int < Any = defer:
+                xs.count(i => f(i).now)
+
+            assert(d.eval == 65536)
+        }
+        "foldLeft" in {
+            def f(i: Int): Int < Any = i + 1
+            val d = defer:
+                xs.foldLeft(0)((acc, e) => acc + f(e).now)
+
+            assert(d.eval == 196608)
+        }
+
+    }
+
+    "Array" - {
+        "foldLeft" in {
+            val it: Iterable[Int] = Array(1, 4)
+
+            val zeroOrOne: Int < Choice = Choice.eval(List(0, 1))
+
+            val effect: Int < kyo.Choice = defer:
+                it.foldLeft(0)((acc, n) => acc + zeroOrOne.now)
+
+            val result = Choice.run(effect).eval
+
+            assert(result.contains(0))
+            assert(result.contains(1))
+            assert(result.contains(2))
+            assert(result.size == 4)
+        }
+
+    }
+
+    // fake Iterable
+    def oneShot[A](a: A*): Iterable[A] =
+        val used = new java.util.concurrent.atomic.AtomicBoolean(false)
+        new Iterable[A]:
+            def iterator: Iterator[A] =
+                if !used.compareAndSet(false, true) then
+                    throw new IllegalStateException("Already consumed!")
+                else Iterator(a*)
+
+        end new
+    end oneShot
+
+    "IterableOnce" - {
+        "foldLeft" in {
+            val it: Iterable[Int] = oneShot(1, 2, 3)
+
+            val zeroOrOne: Int < Choice = Choice.eval(List(0, 1))
+
+            val effect: Int < kyo.Choice = defer:
+                it.foldLeft(0)((acc, n) => acc + zeroOrOne.now)
+
+            val result = Choice.run(effect).eval
+
+            assert(result.contains(0))
+            assert(result.contains(1))
+            assert(result.contains(2))
+            assert(result.contains(3))
+            assert(result.size == 8)
+        }
+    }
+    "Choice" - {
+
path: kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala
user: login: hearnadam
body: Could we make this file more generic?

`def testAll[A](constructors*)( (A* => Seq[A]) => Unit)` or something? then we can just write:

`testAll(Chunk.apply, Vector.apply, List.apply) { seq =>  seq(1, 2, 3)`?
created_at: 2025-05-28T17:34:15Z
author_association: COLLABORATOR
diff_hunk: @@ -214,4 +223,96 @@ object directInternal:
 
         override def flatMap[A, B](fa: A < S)(f: A => B < S): B < S = fa.flatMap(f)
     end KyoCpsMonad
+
+    class KyoSeqAsyncShift[A, C[X] >: Chunk[X] <: Seq[X] & SeqOps[X, C, C[X]], CA <: C[A]] extends SeqAsyncShift[A, C, CA]:
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] =>
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: ahoy-jon
body: You are not missing something. Normally never, unless it's used with another CpsMonad.
Removed the noise.
created_at: 2025-05-28T22:23:53Z
author_association: COLLABORATOR
diff_hunk: @@ -297,4 +297,230 @@ class ShiftMethodSupportTest extends AnyFreeSpec with Assertions:
             assert(d.eval == 2)
         }
     }
+
+    "Chunk" - {
+        "map < Any" in {
+            val xs = Chunk(1, 2, 3)
+
+            def f(i: Int): Int < Any = i + 1
+            val d = defer:
+                xs.map(i => f(i).now)
+
+            assert(d.eval == Chunk(2, 3, 4))
+        }
+        "map < Abort" in {
+            val xs = Chunk(1, 2, 3)
+            def f(i: Int): Int < Abort[String] =
+                if i == 2 then Abort.fail("2") else i
+
+            val d = defer:
+                xs.map(i => f(i).now)
+
+            assert(Abort.run(d).eval == Result.fail("2"))
+        }
+
+        val xs = Chunk.fill(n = 0xffff + 1)(elem = 2)
+        "find" in {
+            def f(i: Int): Boolean < Any = i == 2
+            val d = defer:
+                xs.find(i => f(i).now)
+
+            assert(d.eval == Option(2))
+        }
+        "count" in {
+            def f(i: Int): Boolean < Any = i > 0
+            val d: Int < Any = defer:
+                xs.count(i => f(i).now)
+
+            assert(d.eval == 65536)
+        }
+        "foldLeft" in {
+            def f(i: Int): Int < Any = i + 1
+            val d = defer:
+                xs.foldLeft(0)((acc, e) => acc + f(e).now)
+
+            assert(d.eval == 196608)
+        }
+
+    }
+
+    "Array" - {
+        "foldLeft" in {
+            val it: Iterable[Int] = Array(1, 4)
+
+            val zeroOrOne: Int < Choice = Choice.eval(List(0, 1))
+
+            val effect: Int < kyo.Choice = defer:
+                it.foldLeft(0)((acc, n) => acc + zeroOrOne.now)
+
+            val result = Choice.run(effect).eval
+
+            assert(result.contains(0))
+            assert(result.contains(1))
+            assert(result.contains(2))
+            assert(result.size == 4)
+        }
+
+    }
+
+    // fake Iterable
+    def oneShot[A](a: A*): Iterable[A] =
+        val used = new java.util.concurrent.atomic.AtomicBoolean(false)
+        new Iterable[A]:
+            def iterator: Iterator[A] =
+                if !used.compareAndSet(false, true) then
+                    throw new IllegalStateException("Already consumed!")
+                else Iterator(a*)
+
+        end new
+    end oneShot
+
+    "IterableOnce" - {
+        "foldLeft" in {
+            val it: Iterable[Int] = oneShot(1, 2, 3)
+
+            val zeroOrOne: Int < Choice = Choice.eval(List(0, 1))
+
+            val effect: Int < kyo.Choice = defer:
+                it.foldLeft(0)((acc, n) => acc + zeroOrOne.now)
+
+            val result = Choice.run(effect).eval
+
+            assert(result.contains(0))
+            assert(result.contains(1))
+            assert(result.contains(2))
+            assert(result.contains(3))
+            assert(result.size == 8)
+        }
+    }
+    "Choice" - {
+
path: kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala
user: login: ahoy-jon
body: That's a great idea! Will do that.
created_at: 2025-05-28T22:24:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package kyo
+
+import cps.CpsMonad
+import cps.runtime.IterableOpsAsyncShift
+import directInternal.KyoCpsMonad
+import kyo.Kyo.toIndexed
+import kyo.kernel.Loop
+import kyo.kernel.internal.Safepoint
+import scala.collection.IterableOps
+import scala.collection.immutable.LinearSeq
+
+trait asyncShiftInternalLowPriorityImplicit1:
+
+    transparent inline given shiftedIterableOps[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]]]
+        : asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]] =
+        asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]]()
+end asyncShiftInternalLowPriorityImplicit1
+
+object asyncShiftInternal extends asyncShiftInternalLowPriorityImplicit1:
+    given Frame = Frame.internal
+
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A] = new ChunkAsyncShift[A]
+
+    class ChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+    class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]] extends IterableOpsAsyncShift[A, C, CA]:
+
+        extension [S, X](chunk: Chunk[X] < S)
+            def resultInto(c: CA): C[X] < S = chunk.map(ch =>
+                c.iterableFactory.from(ch)
path: kyo-direct/shared/src/main/scala/kyo/AsyncShift.scala
user: login: ahoy-jon
body: it doesn't rewrite `Chunk[?]` if it's already a `Chunk[?]`
created_at: 2025-05-29T00:05:14Z
author_association: COLLABORATOR
diff_hunk: @@ -475,7 +475,7 @@ object Kyo:
                                 val curr = seq.head
                                 f(curr).map:
                                     case true  => Loop.continue(seq.tail)
-                                    case false => Loop.done(Chunk.from(seq.tail))
+                                    case false => Loop.done(Chunk.from(seq))
path: kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
user: login: hearnadam
body: nice catch!
created_at: 2025-05-29T00:17:48Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package kyo
+
+import cps.CpsMonad
+import cps.runtime.IterableOpsAsyncShift
+import directInternal.KyoCpsMonad
+import kyo.Kyo.toIndexed
+import kyo.kernel.Loop
+import kyo.kernel.internal.Safepoint
+import scala.collection.IterableOps
+import scala.collection.immutable.LinearSeq
+
+trait asyncShiftInternalLowPriorityImplicit1:
+
+    transparent inline given shiftedIterableOps[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]]]
+        : asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]] =
+        asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]]()
+end asyncShiftInternalLowPriorityImplicit1
+
+object asyncShiftInternal extends asyncShiftInternalLowPriorityImplicit1:
+    given Frame = Frame.internal
+
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A] = new ChunkAsyncShift[A]
+
+    class ChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+    class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]] extends IterableOpsAsyncShift[A, C, CA]:
+
+        extension [S, X](chunk: Chunk[X] < S)
+            def resultInto(c: CA): C[X] < S = chunk.map(ch =>
+                c.iterableFactory.from(ch)
+            )
+        end extension
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+        end shiftedFold
+
+        override def shiftedStateFold[F[_], S, R](c: CA, monad: CpsMonad[F])(prolog: S, acc: (S, A) => F[S], epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foldLeft(c)(prolog)((s, a) => acc(s, a)).map(s => epilog(s))
+
+        override def shiftedWhile[F[_], S, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: S, condition: A => F[Boolean], acc: (S, Boolean, A) => S, epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] => asyncShiftInternal.shiftedWhile(c)(prolog, condition, acc, epilog)
+
+        override def dropWhile[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[C[A]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.dropWhile(c)(a => p(a)).resultInto(c)
+
+        override def filter[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[C[A]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.filter(c)(a => p(a)).resultInto(c)
+
+        override def takeWhile[F[_]](c: CA, monad: CpsMonad[F])(p: (A) => F[Boolean]): F[C[A]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.takeWhile(c)(a => p(a)).resultInto(c)
+
+        override def map[F[_], B](c: CA, monad: CpsMonad[F])(f: A => F[B]): F[C[B]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreach(c)(a => f(a)).resultInto(c)
+        end map
+
+        override def foreach[F[_], U](c: CA, monad: CpsMonad[F])(f: A => F[U]): F[Unit] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreachDiscard(c)(a => f(a))
+    end KyoSeqAsyncShift
+
+    private[kyo] def shiftedWhile[A, S, B, C](source: IterableOnce[A])(
path: kyo-direct/shared/src/main/scala/kyo/AsyncShift.scala
user: login: hearnadam
body: this could potentially be ported to `Kyo` file? Fine to followup - and I am not sure how awkward it would be.
created_at: 2025-05-29T00:18:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package kyo
+
+import cps.CpsMonad
+import cps.runtime.IterableOpsAsyncShift
+import directInternal.KyoCpsMonad
+import kyo.Kyo.toIndexed
+import kyo.kernel.Loop
+import kyo.kernel.internal.Safepoint
+import scala.collection.IterableOps
+import scala.collection.immutable.LinearSeq
+
+trait asyncShiftInternalLowPriorityImplicit1:
+
+    transparent inline given shiftedIterableOps[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]]]
+        : asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]] =
+        asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]]()
+end asyncShiftInternalLowPriorityImplicit1
+
+object asyncShiftInternal extends asyncShiftInternalLowPriorityImplicit1:
+    given Frame = Frame.internal
+
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A] = new ChunkAsyncShift[A]
+
+    class ChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+    class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]] extends IterableOpsAsyncShift[A, C, CA]:
+
+        extension [S, X](chunk: Chunk[X] < S)
+            def resultInto(c: CA): C[X] < S = chunk.map(ch =>
+                c.iterableFactory.from(ch)
path: kyo-direct/shared/src/main/scala/kyo/AsyncShift.scala
user: login: hearnadam
body: https://github.com/getkyo/kyo/blob/main/kyo-data/shared/src/main/scala/kyo/Chunk.scala#L611
created_at: 2025-05-29T00:20:20Z
author_association: COLLABORATOR
diff_hunk: @@ -475,7 +475,7 @@ object Kyo:
                                 val curr = seq.head
                                 f(curr).map:
                                     case true  => Loop.continue(seq.tail)
-                                    case false => Loop.done(Chunk.from(seq.tail))
+                                    case false => Loop.done(Chunk.from(seq))
path: kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
user: login: ahoy-jon
body: Yes, we need more generic tests!
created_at: 2025-05-29T00:25:33Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package kyo
+
+import cps.CpsMonad
+import cps.runtime.IterableOpsAsyncShift
+import directInternal.KyoCpsMonad
+import kyo.Kyo.toIndexed
+import kyo.kernel.Loop
+import kyo.kernel.internal.Safepoint
+import scala.collection.IterableOps
+import scala.collection.immutable.LinearSeq
+
+trait asyncShiftInternalLowPriorityImplicit1:
+
+    transparent inline given shiftedIterableOps[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]]]
+        : asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]] =
+        asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]]()
+end asyncShiftInternalLowPriorityImplicit1
+
+object asyncShiftInternal extends asyncShiftInternalLowPriorityImplicit1:
+    given Frame = Frame.internal
+
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A] = new ChunkAsyncShift[A]
+
+    class ChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+    class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]] extends IterableOpsAsyncShift[A, C, CA]:
+
+        extension [S, X](chunk: Chunk[X] < S)
+            def resultInto(c: CA): C[X] < S = chunk.map(ch =>
+                c.iterableFactory.from(ch)
+            )
+        end extension
+
+        override def shiftedFold[F[_], Acc, B, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: Acc, action: A => F[B], acc: (Acc, A, B) => Acc, epilog: Acc => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] =>
+                    Kyo.foldLeft(c)(prolog)((state, a) =>
+                        action(a).map(b => acc(state, a, b))
+                    ).map(s => epilog(s))
+        end shiftedFold
+
+        override def shiftedStateFold[F[_], S, R](c: CA, monad: CpsMonad[F])(prolog: S, acc: (S, A) => F[S], epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foldLeft(c)(prolog)((s, a) => acc(s, a)).map(s => epilog(s))
+
+        override def shiftedWhile[F[_], S, R](
+            c: CA,
+            monad: CpsMonad[F]
+        )(prolog: S, condition: A => F[Boolean], acc: (S, Boolean, A) => S, epilog: S => R): F[R] =
+            monad match
+                case _: KyoCpsMonad[?] => asyncShiftInternal.shiftedWhile(c)(prolog, condition, acc, epilog)
+
+        override def dropWhile[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[C[A]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.dropWhile(c)(a => p(a)).resultInto(c)
+
+        override def filter[F[_]](c: CA, monad: CpsMonad[F])(p: A => F[Boolean]): F[C[A]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.filter(c)(a => p(a)).resultInto(c)
+
+        override def takeWhile[F[_]](c: CA, monad: CpsMonad[F])(p: (A) => F[Boolean]): F[C[A]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.takeWhile(c)(a => p(a)).resultInto(c)
+
+        override def map[F[_], B](c: CA, monad: CpsMonad[F])(f: A => F[B]): F[C[B]] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreach(c)(a => f(a)).resultInto(c)
+        end map
+
+        override def foreach[F[_], U](c: CA, monad: CpsMonad[F])(f: A => F[U]): F[Unit] =
+            monad match
+                case _: KyoCpsMonad[?] => Kyo.foreachDiscard(c)(a => f(a))
+    end KyoSeqAsyncShift
+
+    private[kyo] def shiftedWhile[A, S, B, C](source: IterableOnce[A])(
path: kyo-direct/shared/src/main/scala/kyo/AsyncShift.scala
user: login: ahoy-jon
body: FI: https://github.com/getkyo/kyo/pull/1212#discussion_r210797875
created_at: 2025-05-29T00:27:22Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,109 @@
+package kyo
+
+import cps.CpsMonad
+import cps.runtime.IterableOpsAsyncShift
+import directInternal.KyoCpsMonad
+import kyo.Kyo.toIndexed
+import kyo.kernel.Loop
+import kyo.kernel.internal.Safepoint
+import scala.collection.IterableOps
+import scala.collection.immutable.LinearSeq
+
+trait asyncShiftInternalLowPriorityImplicit1:
+
+    transparent inline given shiftedIterableOps[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]]]
+        : asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]] =
+        asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]]()
+end asyncShiftInternalLowPriorityImplicit1
+
+object asyncShiftInternal extends asyncShiftInternalLowPriorityImplicit1:
+    given Frame = Frame.internal
+
+    transparent inline given shiftedChunk[A]: ChunkAsyncShift[A] = new ChunkAsyncShift[A]
+
+    class ChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+
+    class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]] extends IterableOpsAsyncShift[A, C, CA]:
+
+        extension [S, X](chunk: Chunk[X] < S)
+            def resultInto(c: CA): C[X] < S = chunk.map(ch =>
+                c.iterableFactory.from(ch)
path: kyo-direct/shared/src/main/scala/kyo/AsyncShift.scala
user: login: ahoy-jon
body: Same for `Seq[?]` https://github.com/scala/scala/blob/f4ca74b9bf5b670f1da2cb5de1586e1b136bc1f9/src/library/scala/collection/immutable/Seq.scala#L41
created_at: 2025-05-29T00:29:53Z
author_association: COLLABORATOR
diff_hunk: @@ -57,244 +61,404 @@ class ShiftTest extends AnyFreeSpec with Assertions:
 end ShiftTest
 
 class ShiftMethodSupportTest extends AnyFreeSpec with Assertions:
-    "Iterable" - {
-        val xs: Iterable[Int < Any] = Seq(1, 2, 3, 4)
-        val xsValues: Iterable[Int] = Seq(1, 2, 3, 4)
-        "collectFirst" in {
-            val d: Option[Int] < Any = defer:
-                xs.collectFirst:
-                    case i => i.now
-            d.map(res => assert(res == Option(1)))
-        }
-        "foreach" in {
-            val d: Int < Emit[Int] = defer:
-                xs.foreach(i =>
-                    val v = i.now
-                    Emit.value(v).unit.now
-                )
-                1 // to avoid https://github.com/getkyo/kyo/issues/903
-            Emit.run(d).map((vs, _) => assert(vs == Seq(1, 2, 3, 4)))
-        }
-        "corresponds" in {
-            val d: Boolean < Any = defer:
-                xs.corresponds(xs)((a, b) => a.now == b.now)
-
-            assert(d.eval)
-        }
-        "count" in {
-            val d: Int < Any = defer:
-                xs.count(_.now > 0)
-
-            assert(d.eval == 4)
-        }
-        "exists" in {
-            val d: Boolean < Any = defer:
-                xs.exists(_.now > 0)
-
-            assert(d.eval)
-        }
-        "forall" in {
-            val d: Boolean < Any = defer:
-                xs.forall(_.now > 0)
-
-            assert(d.eval)
-        }
-        "find" in {
-            val d: Option[Int] < Any = defer:
-                xs.find(_.now > 2).map(_.now)
-
-            assert(d.eval == Option(3))
-        }
-        "fold" in {
-            val d: Int < Any = defer:
-                def inc(i: Int): Int < Any = (i + 1).later
-
-                xsValues.fold[Int](0)((a, b) => inc(a + b).now)
-
-            assert(d.eval == 14)
-        }
-        "foldLeft" in {
-            val d = defer:
-                xs.foldLeft(0)(_ + _.now)
-
-            assert(d.eval == 10)
-        }
-        "foldRight" in {
-            val d = defer:
-                xs.foldRight(0)(_.now + _)
-
-            assert(d.eval == 10)
-        }
-        "groupMapReduce" in {
-            val d = defer:
-                xs.groupMapReduce(_ => 1)(_.now)(_ + _)
-
-            assert(d.eval == Map(1 -> 10))
-        }
-        "maxByOpOption" in {
-            val d: Option[Int] < Any = defer:
-                xs.maxByOption(_.now).map(_.now)
 
-            assert(d.eval == Option(4))
+    def iterableTests[Coll[X] <: Iterable[X] & IterableOps[X, Coll, Coll[X]]](
+        name: String,
+        builder: [X] => Seq[X] => Coll[X]
+    ): Unit =
+        def Coll[X](x: X*): Coll[X] = builder(x)
+
+        name - {
+            val xs: Coll[Int < Any] = Coll(1, 2, 3, 4)
+            val xsValues: Coll[Int] = Coll(1, 2, 3, 4)
+
+            "collectFirst" in {
+                val d: Option[Int] < Any = defer:
+                    xs.collectFirst:
+                        case i => i.now
+                d.map(res => assert(res == Option(1)))
+            }
+            "foreach" in {
+                val d: Unit < Emit[Int] = defer:
+                    xs.foreach(i =>
+                        val v = i.now
+                        Emit.value(v).unit.now
+                    )
+
+                Emit.run(d).map((vs, _) => assert(vs == Seq(1, 2, 3, 4)))
+            }
+            "corresponds" in {
+                val d: Boolean < Any = defer:
+                    xs.corresponds(xs)((a, b) => a.now == b.now)
+
+                assert(d.eval)
+            }
+            "count" in {
+                val d: Int < Any = defer:
+                    xs.count(_.now > 0)
+
+                assert(d.eval == 4)
+            }
+            "exists" in {
+                val d: Boolean < Any = defer:
+                    xs.exists(_.now > 0)
+
+                assert(d.eval)
+            }
+            "forall" in {
+                val d: Boolean < Any = defer:
+                    xs.forall(_.now > 0)
+
+                assert(d.eval)
+            }
+            "find" in {
+                val d: Option[Int] < Any = defer:
+                    xs.find(_.now > 2).map(_.now)
+
+                assert(d.eval == Option(3))
+            }
+            "fold" in {
+                val d: Int < Any = defer:
+
+                    def inc(i: Int): Int < Any = (i + 1).later
+
+                    xsValues.fold[Int](0)((a, b) => inc(a + b).now)
+
+                assert(d.eval == 14)
+            }
+            "foldLeft" in {
+                val d = defer:
+                    xs.foldLeft(0)(_ + _.now)
+
+                assert(d.eval == 10)
+            }
+            "foldRight" in {
+                val d = defer:
+                    xs.foldRight(0)(_.now + _)
+
+                assert(d.eval == 10)
+            }
+            "groupMapReduce" in {
+                val d = defer:
+                    xs.groupMapReduce(_ => 1)(_.now)(_ + _)
+
+                assert(d.eval == Map(1 -> 10))
+            }
+            "maxByOpOption" in {
+                val d: Option[Int] < Any = defer:
+                    xs.maxByOption(_.now).map(_.now)
+
+                assert(d.eval == Option(4))
+            }
+            /*"reduceOption" in {
+                val d: Option[Int] < Any = defer:
+                    xs.reduceOption(_.now + _.now).map(_.now)
+    
+                assert(d.eval == Option(10))
+            }*/
+            "map" in {
+                val d: Seq[Int] < Any = defer:
+                    xs.map(_.now).toSeq
+
+                assert(d.eval == Seq(1, 2, 3, 4))
+            }
+            "flatMap" in {
+                val d: Seq[Int] < Any = defer:
+
+                    def f(i: Int) = Seq(1)
+
+                    xs.flatMap(i => f(i.now)).toSeq.distinct
+
+                assert(d.eval == Seq(1))
+            }
+            "collect" in {
+                val d: Seq[Int] < Any = defer:
+                    xs.collect({
+                        case i => i.now
+                    }).toSeq
+
+                assert(d.eval == Seq(1, 2, 3, 4))
+            }
+            "dropWhile" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    Seq(1, 2, 3, 4).dropWhile(f(_).now).toSeq
+
+                assert(d.eval == xsValues.dropWhile(_ < 3).toSeq)
+            }
+            "filter" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.filter(i => f(i).now).toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "filterNot" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.filterNot(i => f(i).now).toSeq
+
+                assert(d.eval == Seq(3, 4))
+            }
+            "groupBy" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.groupBy(i => f(i).now).view.mapValues(_.toSeq).toMap
+
+                assert(d.eval == Map(true -> Seq(1, 2), false -> Seq(3, 4)))
+            }
+            "groupMap" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                // TODO : catch xsValues.groupMap(i => f(i))( ...
+                val d = defer:
+                    xsValues.groupMap(i => f(i).now)(i => f(i).now).view.mapValues(_.toSeq.distinct).toMap
+
+                assert(d.eval == Map(true -> Seq(true), false -> Seq(false)))
+            }
+            "scanLeft" in {
+                val d = defer:
+                    xs.scanLeft(0)((b, e) => b + e.now).toSeq
+
+                assert(d.eval == Seq(0, 1, 3, 6, 10))
+            }
+            "scanRight" in {
+                val d = defer:
+                    xs.scanRight(0)((e, b) => e.now + b).toSeq
+
+                assert(d.eval == Seq(10, 9, 7, 4, 0))
+            }
+            "span" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    val (take, drop) = xsValues.span(i => f(i).now)
+                    take.toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "takeWhile" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.takeWhile(i => f(i).now).toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "partition" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    val (pTrue, _) = xsValues.partition(i => f(i).now)
+                    pTrue.toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+            "partitionMap" in {
+                def f(i: Int): Either[String, Int] < Any = if i < 3 then
+                    Left(i.toString)
+                else Right(i)
+
+                val d = defer:
+                    val (pLeft, pRight) = xsValues.partitionMap(i => f(i).now)
+                    pLeft.toSeq
+
+                assert(d.eval == Seq("1", "2"))
+            }
+            "tapEach" in {
+                def f(i: Int): Unit < Emit[Int] = Emit.value(i)
+
+                val d: Seq[Int] < Emit[Int] = defer:
+                    xsValues.tapEach(i => f(i).now).toSeq
+
+                val (es, res) = Emit.run(d).eval
+                assert(es == res)
+            }
+            "withFilter" in {
+                def f(i: Int): Boolean < Any = i < 3
+
+                val d = defer:
+                    xsValues.withFilter(i => f(i).now).map(x => x).toSeq
+
+                assert(d.eval == Seq(1, 2))
+            }
+
+            "Choice" - {
+                "foreach" in {
+                    val result = Choice.run(
+                        defer:
+                            Coll("x", "y").map(str =>
+                                Choice.eval(List(true, false))
+                                    .map(b => if b then str.toUpperCase else str).now
+                            )
+                    ).eval
+
+                    assert(result.contains(Chunk("X", "Y")))
+                    assert(result.contains(Chunk("X", "y")))
+                    assert(result.contains(Chunk("x", "Y")))
+                    assert(result.contains(Chunk("x", "y")))
+                    assert(result.size == 4)
+                }
+
+                "collect" in {
+                    val effects =
+                        Coll("x", "y").map { str =>
+                            Choice.eval(List(true, false)).map(b =>
+                                if b then str.toUpperCase else str
+                            )
+                        }
+                    val result = Choice.run(defer(effects.map(_.now))).eval
+
+                    assert(result.contains(Chunk("X", "Y")))
+                    assert(result.contains(Chunk("X", "y")))
+                    assert(result.contains(Chunk("x", "Y")))
+                    assert(result.contains(Chunk("x", "y")))
+                    assert(result.size == 4)
+                }
+
+                "flatMap" in {
+                    val effects = defer:
+                        val tf = Choice.eval(List(true, false)).later
+                        Coll("x", "y").flatMap(str1 =>
+                            val pred1 = tf.now
+                            Coll("z").map(str2 =>
+                                val pred2 = tf.now
+                                if pred1 & pred2 then
+                                    (str1 + str2).toUpperCase
+                                else if pred1 != pred2 then
+                                    Choice.drop.now
+                                else (str1 + str2)
+                                end if
+                            )
+                        )
+
+                    val result = Choice.run(effects).eval
+                    assert(result.contains(Chunk("XZ", "YZ")))
+                    assert(result.contains(Chunk("XZ", "yz")))
+                    assert(result.contains(Chunk("xz", "YZ")))
+                    assert(result.contains(Chunk("xz", "yz")))
+                    assert(result.size == 4)
+                }
+
+                "foldLeft" in {
+                    val result = Choice.run(
+                        defer:
+                            Coll(1, 1).foldLeft(0)((acc, _) => Choice.eval(List(0, 1)).map(n => acc + n).now)
+                    ).eval
+
+                    assert(result.contains(0))
+                    assert(result.contains(1))
+                    assert(result.contains(2))
+                    assert(result.size == 4)
+                }
+            }
         }
-        /*"reduceOption" in {
-            val d: Option[Int] < Any = defer:
-                xs.reduceOption(_.now + _.now).map(_.now)
-
-            assert(d.eval == Option(10))
-        }*/
-        "map" in {
-            val d: Seq[Int] < Any = defer:
-                xs.map(_.now).toSeq
+    end iterableTests
 
-            assert(d.eval == Seq(1, 2, 3, 4))
-        }
-        "flatMap" in {
-            val d: Seq[Int] < Any = defer:
-                def f(i: Int) = Seq(1)
-                xs.flatMap(i => f(i.now)).toSeq.distinct
+    iterableTests[Iterable]("Iterable", [X] => seq => seq)
+    iterableTests[Vector]("Vector", [X] => seq => Vector.from(seq))
+    iterableTests[Chunk]("Chunk", [X] => seq => Chunk.from(seq))
+    iterableTests[List]("List", [X] => seq => List.from(seq))
path: kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala
user: login: ahoy-jon
body: @hearnadam it is generalized on the collection structures, but not on the implicit selection from `dotty-cps-async`.
So far inlines are not solving it.
created_at: 2025-05-29T13:14:16Z
author_association: COLLABORATOR
diff_hunk: @@ -3,9 +3,16 @@ package kyo
 import cps.CpsMonad
 import cps.CpsMonadContext
 import cps.async
-import directInternal.*
+import cps.runtime.SeqAsyncShift
+import directInternal.KyoCpsMonad
 import kyo.Ansi.*
+import kyo.Kyo.toIndexed
+import kyo.kernel.Loop
+import kyo.kernel.internal.Safepoint
 import scala.annotation.tailrec
+import scala.collection.Factory
+import scala.collection.SeqOps
+import scala.collection.immutable.LinearSeq
path: kyo-direct/shared/src/main/scala/kyo/Direct.scala
user: login: hearnadam
body: I think most of these are unused?
created_at: 2025-05-29T14:02:56Z
author_association: COLLABORATOR
diff_hunk: @@ -57,244 +61,404 @@ class ShiftTest extends AnyFreeSpec with Assertions:
 end ShiftTest
 
 class ShiftMethodSupportTest extends AnyFreeSpec with Assertions:
-    "Iterable" - {
-        val xs: Iterable[Int < Any] = Seq(1, 2, 3, 4)
-        val xsValues: Iterable[Int] = Seq(1, 2, 3, 4)
-        "collectFirst" in {
-            val d: Option[Int] < Any = defer:
-                xs.collectFirst:
-                    case i => i.now
-            d.map(res => assert(res == Option(1)))
-        }
-        "foreach" in {
-            val d: Int < Emit[Int] = defer:
-                xs.foreach(i =>
-                    val v = i.now
-                    Emit.value(v).unit.now
-                )
-                1 // to avoid https://github.com/getkyo/kyo/issues/903
-            Emit.run(d).map((vs, _) => assert(vs == Seq(1, 2, 3, 4)))
-        }
-        "corresponds" in {
-            val d: Boolean < Any = defer:
-                xs.corresponds(xs)((a, b) => a.now == b.now)
-
-            assert(d.eval)
-        }
-        "count" in {
-            val d: Int < Any = defer:
-                xs.count(_.now > 0)
-
-            assert(d.eval == 4)
-        }
-        "exists" in {
-            val d: Boolean < Any = defer:
-                xs.exists(_.now > 0)
-
-            assert(d.eval)
-        }
-        "forall" in {
-            val d: Boolean < Any = defer:
-                xs.forall(_.now > 0)
-
-            assert(d.eval)
-        }
-        "find" in {
-            val d: Option[Int] < Any = defer:
-                xs.find(_.now > 2).map(_.now)
-
-            assert(d.eval == Option(3))
-        }
-        "fold" in {
-            val d: Int < Any = defer:
-                def inc(i: Int): Int < Any = (i + 1).later
-
-                xsValues.fold[Int](0)((a, b) => inc(a + b).now)
-
-            assert(d.eval == 14)
-        }
-        "foldLeft" in {
-            val d = defer:
-                xs.foldLeft(0)(_ + _.now)
-
-            assert(d.eval == 10)
-        }
-        "foldRight" in {
-            val d = defer:
-                xs.foldRight(0)(_.now + _)
-
-            assert(d.eval == 10)
-        }
-        "groupMapReduce" in {
-            val d = defer:
-                xs.groupMapReduce(_ => 1)(_.now)(_ + _)
-
-            assert(d.eval == Map(1 -> 10))
-        }
-        "maxByOpOption" in {
-            val d: Option[Int] < Any = defer:
-                xs.maxByOption(_.now).map(_.now)
 
-            assert(d.eval == Option(4))
+    def iterableTests[Coll[X] <: Iterable[X] & IterableOps[X, Coll, Coll[X]]](
+        name: String,
+        builder: [X] => Seq[X] => Coll[X]
+    ): Unit =
+        def Coll[X](x: X*): Coll[X] = builder(x)
+
+        name - {
+            val xs: Coll[Int < Any] = Coll(1, 2, 3, 4)
+            val xsValues: Coll[Int] = Coll(1, 2, 3, 4)
+
+            "collectFirst" in {
+                val d: Option[Int] < Any = defer:
+                    xs.collectFirst:
+                        case i => i.now
+                d.map(res => assert(res == Option(1)))
+            }
+            "foreach" in {
+                val d: Unit < Emit[Int] = defer:
+                    xs.foreach(i =>
+                        val v = i.now
+                        Emit.value(v).unit.now
+                    )
+
+                Emit.run(d).map((vs, _) => assert(vs == Seq(1, 2, 3, 4)))
+            }
+            "corresponds" in {
+                val d: Boolean < Any = defer:
+                    xs.corresponds(xs)((a, b) => a.now == b.now)
+
+                assert(d.eval)
+            }
+            "count" in {
+                val d: Int < Any = defer:
+                    xs.count(_.now > 0)
+
+                assert(d.eval == 4)
+            }
+            "exists" in {
+                val d: Boolean < Any = defer:
+                    xs.exists(_.now > 0)
+
+                assert(d.eval)
+            }
+            "forall" in {
+                val d: Boolean < Any = defer:
+                    xs.forall(_.now > 0)
+
+                assert(d.eval)
+            }
+            "find" in {
+                val d: Option[Int] < Any = defer:
+                    xs.find(_.now > 2).map(_.now)
+
+                assert(d.eval == Option(3))
+            }
+            "fold" in {
+                val d: Int < Any = defer:
+
+                    def inc(i: Int): Int < Any = (i + 1).later
+
+                    xsValues.fold[Int](0)((a, b) => inc(a + b).now)
+
+                assert(d.eval == 14)
+            }
+            "foldLeft" in {
+                val d = defer:
+                    xs.foldLeft(0)(_ + _.now)
+
+                assert(d.eval == 10)
+            }
+            "foldRight" in {
+                val d = defer:
+                    xs.foldRight(0)(_.now + _)
+
+                assert(d.eval == 10)
+            }
+            "groupMapReduce" in {
+                val d = defer:
+                    xs.groupMapReduce(_ => 1)(_.now)(_ + _)
+
+                assert(d.eval == Map(1 -> 10))
+            }
+            "maxByOpOption" in {
+                val d: Option[Int] < Any = defer:
+                    xs.maxByOption(_.now).map(_.now)
+
+                assert(d.eval == Option(4))
+            }
+            /*"reduceOption" in {
+                val d: Option[Int] < Any = defer:
+                    xs.reduceOption(_.now + _.now).map(_.now)
+    
+                assert(d.eval == Option(10))
+            }*/
path: kyo-direct/shared/src/test/scala/kyo/ShiftTest.scala
user: login: hearnadam
body: minor, but prefer `typecheck` so this gets cleaned up later.
created_at: 2025-05-29T14:04:02Z
author_association: COLLABORATOR
diff_hunk: @@ -5,6 +5,7 @@ import scala.annotation.tailrec
 import scala.annotation.targetName
 import scala.collection.IterableFactoryDefaults
 import scala.collection.StrictOptimizedSeqFactory
+import scala.collection.immutable.IndexedSeqOps
path: kyo-data/shared/src/main/scala/kyo/Chunk.scala
user: login: hearnadam
body: Still used?
created_at: 2025-05-29T14:07:43Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,53 @@
+package kyo.internal
+
+import kyo.*
+import kyo.Kyo.shiftedWhile
path: kyo-direct/shared/src/test/scala/kyo/internal/DirectInternalTest.scala
user: login: hearnadam
body: Should this go in the KyoTest now?
created_at: 2025-05-29T17:42:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,143 @@
+package kyo
+
+import cps.CpsMonad
+import cps.runtime.ArrayOpsAsyncShift
+import cps.runtime.IterableOpsAsyncShift
+import kyo.directInternal.KyoCpsMonad
+import kyo.kernel.internal.Safepoint
+import scala.annotation.targetName
+import scala.collection.ArrayOps
+import scala.collection.IterableOps
+import scala.reflect.ClassTag
+
+trait asyncShiftInternalLowPriorityImplicit1:
+    transparent inline given shiftedIterableOps[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]]]
+        : asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]] =
+        asyncShiftInternal.KyoSeqAsyncShift[A, C, C[A]]()
+end asyncShiftInternalLowPriorityImplicit1
+
+object asyncShiftInternal extends asyncShiftInternalLowPriorityImplicit1:
+    given Frame = Frame.internal
path: kyo-direct/shared/src/main/scala/kyo/AsyncShift.scala
user: login: hearnadam
body: I think `KyoSeqAsyncShift` can accept a `using Frame`, which should be inlined at the given?
created_at: 2025-05-29T17:44:34Z
author_association: COLLABORATOR
diff_hunk: @@ -5,6 +5,7 @@ import scala.annotation.tailrec
 import scala.annotation.targetName
 import scala.collection.IterableFactoryDefaults
 import scala.collection.StrictOptimizedSeqFactory
+import scala.collection.immutable.IndexedSeqOps
path: kyo-data/shared/src/main/scala/kyo/Chunk.scala
user: login: ahoy-jon
body: Indeed, at one point I changed `Chunk[A]` to a `IndexedSeq[A]`, then I realized it's not wise.
created_at: 2025-05-29T17:54:40Z
author_association: COLLABORATOR
diff_hunk: @@ -17,14 +17,14 @@ trait asyncShiftInternalLowPriorityImplicit1:
 end asyncShiftInternalLowPriorityImplicit1
 
 object asyncShiftInternal extends asyncShiftInternalLowPriorityImplicit1:
-    given Frame = Frame.internal
 
     transparent inline given shiftedChunk[A]: ChunkAsyncShift[A]       = new ChunkAsyncShift[A]
     transparent inline given shiftedArrayOps[A]: ArrayOpsAsyncShift[A] = new KyoArrayOpsAsyncShift[A]
 
-    class ChunkAsyncShift[A] extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
+    class ChunkAsyncShift[A](using Frame) extends KyoSeqAsyncShift[A, Chunk, Chunk[A]]
 
-    class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]] extends IterableOpsAsyncShift[A, C, CA]:
+    class KyoSeqAsyncShift[A, C[X] <: Iterable[X] & IterableOps[X, C, C[X]], CA <: C[A]](using Frame)
+        extends IterableOpsAsyncShift[A, C, CA]:
path: kyo-direct/shared/src/main/scala/kyo/AsyncShift.scala
user: login: ahoy-jon
body: @hearnadam is that enough for Frames?
created_at: 2025-05-29T18:07:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,130 @@
+package kyo.directInternal
path: kyo-direct/shared/src/main/scala/kyo/directInternal/AsyncShift.scala
user: login: hearnadam
body: Any reason this isn't just `internal`?
created_at: 2025-05-29T23:06:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,27 @@
+package kyo.directInternal
+
+import cps.CpsMonad
+import cps.CpsMonadContext
+import kyo.*
+import kyo.kernel.internal.Safepoint
+
+class KyoCpsMonad[S]
path: kyo-direct/shared/src/main/scala/kyo/directInternal/KyoCpsMonad.scala
user: login: hearnadam
body: ```suggestion
final class KyoCpsMonad[S]
```
created_at: 2025-05-29T23:07:44Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,27 @@
+package kyo.directInternal
+
+import cps.CpsMonad
+import cps.CpsMonadContext
+import kyo.*
+import kyo.kernel.internal.Safepoint
+
+class KyoCpsMonad[S]
+    extends CpsMonadContext[[A] =>> A < S]
+    with CpsMonad[[A] =>> A < S]
+    with Serializable:
+
+    given Frame = Frame.internal
path: kyo-direct/shared/src/main/scala/kyo/directInternal/KyoCpsMonad.scala
user: login: hearnadam
body: ```suggestion
    private given Frame = Frame.internal
```
created_at: 2025-05-29T23:07:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,27 @@
+package kyo.directInternal
+
+import cps.CpsMonad
+import cps.CpsMonadContext
+import kyo.*
+import kyo.kernel.internal.Safepoint
+
+class KyoCpsMonad[S]
+    extends CpsMonadContext[[A] =>> A < S]
+    with CpsMonad[[A] =>> A < S]
+    with Serializable:
+
+    given Frame = Frame.internal
+
+    type Context = KyoCpsMonad[S]
+
+    override def monad: CpsMonad[[A] =>> A < S] = this
+
+    override def apply[A](op: Context => A < S): A < S = op(this)
+
+    override def pure[A](t: A): A < S = t
+
+    override def map[A, B](fa: A < S)(f: A => B): B < S = flatMap(fa)(f)
path: kyo-direct/shared/src/main/scala/kyo/directInternal/KyoCpsMonad.scala
user: login: hearnadam
body: I think this will be inlined this way:
```suggestion
    override def map[A, B](fa: A < S)(f: A => B): B < S = fa.flatMap(f)
```
created_at: 2025-05-29T23:08:25Z
author_association: COLLABORATOR
diff_hunk: @@ -64,11 +63,11 @@ private[kyo] object Validate:
                     qualifier.tpe <:< TypeRepr.of[IterableOps[?, ?, ?]] |
                     qualifier.tpe <:< TypeRepr.of[Option[?]] |
                     qualifier.tpe <:< TypeRepr.of[scala.util.Try[?]] |
-                    qualifier.tpe <:< TypeRepr.of[MapOps[?, ?, ?, ?]] |
-                    qualifier.tpe <:< TypeRepr.of[ArrayOps[?]] |
+                    // qualifier.tpe <:< TypeRepr.of[MapOps[?, ?, ?, ?]] |
+                    // qualifier.tpe <:< TypeRepr.of[ArrayOps[?]] |
path: kyo-direct/shared/src/main/scala/kyo/directInternal/Validate.scala
user: login: hearnadam
body: ```suggestion
```
created_at: 2025-05-29T23:18:33Z
author_association: COLLABORATOR
diff_hunk: @@ -64,11 +63,11 @@ private[kyo] object Validate:
                     qualifier.tpe <:< TypeRepr.of[IterableOps[?, ?, ?]] |
                     qualifier.tpe <:< TypeRepr.of[Option[?]] |
                     qualifier.tpe <:< TypeRepr.of[scala.util.Try[?]] |
-                    qualifier.tpe <:< TypeRepr.of[MapOps[?, ?, ?, ?]] |
-                    qualifier.tpe <:< TypeRepr.of[ArrayOps[?]] |
+                    // qualifier.tpe <:< TypeRepr.of[MapOps[?, ?, ?, ?]] |
+                    // qualifier.tpe <:< TypeRepr.of[ArrayOps[?]] |
                     qualifier.tpe <:< TypeRepr.of[Either[?, ?]] |
-                    qualifier.tpe <:< TypeRepr.of[Either.LeftProjection[?, ?]] |
-                    qualifier.tpe <:< TypeRepr.of[WithFilter[?, ?]]
+                    qualifier.tpe <:< TypeRepr.of[Either.LeftProjection[?, ?]]
+                // qualifier.tpe <:< TypeRepr.of[WithFilter[?, ?]]
path: kyo-direct/shared/src/main/scala/kyo/directInternal/Validate.scala
user: login: hearnadam
body: ```suggestion
```
created_at: 2025-05-29T23:18:39Z
author_association: COLLABORATOR
diff_hunk: @@ -41,6 +39,7 @@ private val validMethodNamesForAsyncShift = Set(
     "recover",
     "recoverWith",
     "scanLeft",
+    // "reduceOption", //doesn't compile yet
path: kyo-direct/shared/src/main/scala/kyo/directInternal/Validate.scala
user: login: hearnadam
body: ```suggestion
```
created_at: 2025-05-29T23:18:45Z
author_association: COLLABORATOR
diff_hunk: @@ -504,8 +752,41 @@ object Kyo:
             else v.map(t => Loop.continue(acc.append(t)))
         }
 
-    private def toIndexed[A](source: IterableOnce[A]): Seq[A] =
+    private[kyo] def toIndexed[A](source: IterableOnce[A]): Seq[A] =
path: kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
user: login: hearnadam
body: Does this need to be package private now?
```suggestion
    private def toIndexed[A](source: IterableOnce[A]): Seq[A] =
```
created_at: 2025-05-29T23:56:59Z
author_association: COLLABORATOR
diff_hunk: @@ -489,6 +565,178 @@ object Kyo:
                 end match
     end dropWhile
 
+    def partition[S, A](source: IterableOnce[A])(f: Safepoint ?=> A => Boolean < S)(using
+        Frame,
+        Safepoint
+    ): (Chunk[A], Chunk[A]) < S =
+        source.knownSize match
+            case 0 => (Chunk.empty, Chunk.empty)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk.empty[A], Chunk.empty[A]) { (seq, trues, falses) =>
+                            if seq.isEmpty then Loop.done((trues, falses))
+                            else
+                                f(seq.head).map { matches =>
+                                    if matches then
+                                        Loop.continue(seq.tail, trues.append(seq.head), falses)
+                                    else
+                                        Loop.continue(seq.tail, trues, falses.append(seq.head))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk.empty[A], Chunk.empty[A]) { (idx, trues, falses) =>
+                            if idx == arr.length then
+                                Loop.done((trues, falses))
+                            else
+                                f(arr(idx)).map { matches =>
+                                    if matches then
+                                        Loop.continue(trues.append(arr(idx)), falses)
+                                    else
+                                        Loop.continue(trues, falses.append(arr(idx)))
+                                }
+                        }
+
+    def partitionMap[S, A, A1, A2](source: IterableOnce[A])(f: Safepoint ?=> A => Either[A1, A2] < S)(using
+        Frame,
+        Safepoint
+    ): (Chunk[A1], Chunk[A2]) < S =
+        source.knownSize match
+            case 0 => (Chunk.empty, Chunk.empty)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk.empty[A1], Chunk.empty[A2]) { (seq, lefts, rights) =>
+                            if seq.isEmpty then Loop.done((lefts, rights))
+                            else
+                                f(seq.head).map {
+                                    case Left(a1)  => Loop.continue(seq.tail, lefts.append(a1), rights)
+                                    case Right(a2) => Loop.continue(seq.tail, lefts, rights.append(a2))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk.empty[A1], Chunk.empty[A2]) { (idx, lefts, rights) =>
+                            if idx >= arr.length then
+                                Loop.done((lefts, rights))
+                            else
+                                f(arr(idx)).map {
+                                    case Left(a1)  => Loop.continue(lefts.append(a1), rights)
+                                    case Right(a2) => Loop.continue(lefts, rights.append(a2))
+                                }
+                        }
+
+    /** Computes a prefix scan of the collection.
+      *
+      * @param z
+      *   Initial accumulator value
+      * @param op
+      *   Effectful operation that combines accumulator with each element
+      * @return
+      *   Chunk containing all intermediate accumulator states
+      */
+    def scanLeft[S, A, B](source: IterableOnce[A])(z: B)(op: (B, A) => B < S)(using
+        Frame,
+        Safepoint
+    ): Chunk[B] < S =
+        source.knownSize match
+            case 0 => Chunk(z)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk(z), z) { (seq, acc, current) =>
+                            if seq.isEmpty then Loop.done(acc)
+                            else
+                                op(current, seq.head).map { next =>
+                                    Loop.continue(seq.tail, acc.append(next), next)
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk(z), z) { (idx, acc, current) =>
+                            if idx == arr.length then Loop.done(acc)
+                            else
+                                op(current, arr(idx)).map { next =>
+                                    Loop.continue(acc.append(next), next)
+                                }
+                        }
+
+    def groupBy[S, A, K](source: IterableOnce[A])(f: Safepoint ?=> A => K < S)(using
+        Frame,
+        Safepoint
+    ): Map[K, Chunk[A]] < S =
+        source.knownSize match
+            case 0 => Map.empty[K, Chunk[A]]
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Map.empty[K, Chunk[A]]) { (seq, acc) =>
+                            if seq.isEmpty then
+                                Loop.done(acc)
+                            else
+                                val a = seq.head
+                                f(a).map { k =>
+                                    val current = acc.getOrElse(k, Chunk.empty)
+                                    Loop.continue(seq.tail, acc.updated(k, current :+ a))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Map.empty[K, Chunk[A]]) { (idx, acc) =>
+                            if idx == arr.length then
+                                Loop.done(acc)
+                            else
+                                val a = arr(idx)
+                                f(a).map { k =>
+                                    val current = acc.getOrElse(k, Chunk.empty)
+                                    Loop.continue(acc.updated(k, current :+ a))
+                                }
+                        }
+
+    def groupMap[S, A, K, B](source: IterableOnce[A])(key: Safepoint ?=> A => K < S)(f: Safepoint ?=> A => B < S)(using
path: kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
user: login: hearnadam
body: I suppose you are relying on the coverage from `direct` throughout? seems okay, just a bit riskier in regards to cleanup...
created_at: 2025-05-29T23:59:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,130 @@
+package kyo.directInternal
path: kyo-direct/shared/src/main/scala/kyo/directInternal/AsyncShift.scala
user: login: ahoy-jon
body: it was `kyo.directInternal` before, as an object. `kyo.direct.internal` might not be wise, but `kyo.internal` is ok. I will try the change.
created_at: 2025-05-30T00:13:19Z
author_association: COLLABORATOR
diff_hunk: @@ -489,6 +565,178 @@ object Kyo:
                 end match
     end dropWhile
 
+    def partition[S, A](source: IterableOnce[A])(f: Safepoint ?=> A => Boolean < S)(using
+        Frame,
+        Safepoint
+    ): (Chunk[A], Chunk[A]) < S =
+        source.knownSize match
+            case 0 => (Chunk.empty, Chunk.empty)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk.empty[A], Chunk.empty[A]) { (seq, trues, falses) =>
+                            if seq.isEmpty then Loop.done((trues, falses))
+                            else
+                                f(seq.head).map { matches =>
+                                    if matches then
+                                        Loop.continue(seq.tail, trues.append(seq.head), falses)
+                                    else
+                                        Loop.continue(seq.tail, trues, falses.append(seq.head))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk.empty[A], Chunk.empty[A]) { (idx, trues, falses) =>
+                            if idx == arr.length then
+                                Loop.done((trues, falses))
+                            else
+                                f(arr(idx)).map { matches =>
+                                    if matches then
+                                        Loop.continue(trues.append(arr(idx)), falses)
+                                    else
+                                        Loop.continue(trues, falses.append(arr(idx)))
+                                }
+                        }
+
+    def partitionMap[S, A, A1, A2](source: IterableOnce[A])(f: Safepoint ?=> A => Either[A1, A2] < S)(using
+        Frame,
+        Safepoint
+    ): (Chunk[A1], Chunk[A2]) < S =
+        source.knownSize match
+            case 0 => (Chunk.empty, Chunk.empty)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk.empty[A1], Chunk.empty[A2]) { (seq, lefts, rights) =>
+                            if seq.isEmpty then Loop.done((lefts, rights))
+                            else
+                                f(seq.head).map {
+                                    case Left(a1)  => Loop.continue(seq.tail, lefts.append(a1), rights)
+                                    case Right(a2) => Loop.continue(seq.tail, lefts, rights.append(a2))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk.empty[A1], Chunk.empty[A2]) { (idx, lefts, rights) =>
+                            if idx >= arr.length then
+                                Loop.done((lefts, rights))
+                            else
+                                f(arr(idx)).map {
+                                    case Left(a1)  => Loop.continue(lefts.append(a1), rights)
+                                    case Right(a2) => Loop.continue(lefts, rights.append(a2))
+                                }
+                        }
+
+    /** Computes a prefix scan of the collection.
+      *
+      * @param z
+      *   Initial accumulator value
+      * @param op
+      *   Effectful operation that combines accumulator with each element
+      * @return
+      *   Chunk containing all intermediate accumulator states
+      */
+    def scanLeft[S, A, B](source: IterableOnce[A])(z: B)(op: (B, A) => B < S)(using
+        Frame,
+        Safepoint
+    ): Chunk[B] < S =
+        source.knownSize match
+            case 0 => Chunk(z)
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Chunk(z), z) { (seq, acc, current) =>
+                            if seq.isEmpty then Loop.done(acc)
+                            else
+                                op(current, seq.head).map { next =>
+                                    Loop.continue(seq.tail, acc.append(next), next)
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Chunk(z), z) { (idx, acc, current) =>
+                            if idx == arr.length then Loop.done(acc)
+                            else
+                                op(current, arr(idx)).map { next =>
+                                    Loop.continue(acc.append(next), next)
+                                }
+                        }
+
+    def groupBy[S, A, K](source: IterableOnce[A])(f: Safepoint ?=> A => K < S)(using
+        Frame,
+        Safepoint
+    ): Map[K, Chunk[A]] < S =
+        source.knownSize match
+            case 0 => Map.empty[K, Chunk[A]]
+            case _ =>
+                source match
+                    case linearSeq: LinearSeq[A] =>
+                        Loop(linearSeq, Map.empty[K, Chunk[A]]) { (seq, acc) =>
+                            if seq.isEmpty then
+                                Loop.done(acc)
+                            else
+                                val a = seq.head
+                                f(a).map { k =>
+                                    val current = acc.getOrElse(k, Chunk.empty)
+                                    Loop.continue(seq.tail, acc.updated(k, current :+ a))
+                                }
+                        }
+
+                    case other =>
+                        val arr = toIndexed(other)
+                        Loop.indexed(Map.empty[K, Chunk[A]]) { (idx, acc) =>
+                            if idx == arr.length then
+                                Loop.done(acc)
+                            else
+                                val a = arr(idx)
+                                f(a).map { k =>
+                                    val current = acc.getOrElse(k, Chunk.empty)
+                                    Loop.continue(acc.updated(k, current :+ a))
+                                }
+                        }
+
+    def groupMap[S, A, K, B](source: IterableOnce[A])(key: Safepoint ?=> A => K < S)(f: Safepoint ?=> A => B < S)(using
path: kyo-kernel/shared/src/main/scala/kyo/Kyo.scala
user: login: ahoy-jon
body: And the coverage of `direct` is for now better. It checks `LinearSeq` and `Indexed`! I need a change, probably another pull-request.
created_at: 2025-05-30T00:16:12Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,27 @@
+package kyo.directInternal
+
+import cps.CpsMonad
+import cps.CpsMonadContext
+import kyo.*
+import kyo.kernel.internal.Safepoint
+
+class KyoCpsMonad[S]
+    extends CpsMonadContext[[A] =>> A < S]
+    with CpsMonad[[A] =>> A < S]
+    with Serializable:
+
+    given Frame = Frame.internal
+
+    type Context = KyoCpsMonad[S]
+
+    override def monad: CpsMonad[[A] =>> A < S] = this
+
+    override def apply[A](op: Context => A < S): A < S = op(this)
+
+    override def pure[A](t: A): A < S = t
+
+    override def map[A, B](fa: A < S)(f: A => B): B < S = flatMap(fa)(f)
path: kyo-direct/shared/src/main/scala/kyo/directInternal/KyoCpsMonad.scala
user: login: ahoy-jon
body: yes
created_at: 2025-05-30T00:22:50Z
author_association: COLLABORATOR

PR comments:
user: login: ahoy-jon
created_at: 2025-05-26T13:17:02Z
body: 
| Benchmark           | Size=1024 | Size=65536 | Size=262144 | Size=1048576 |
|---------------------|----------:|----------:|-----------:|------------:|
| **deferIterable** (*)   | 1.0x      | 1.0x      | 1.0x       | 1.0x        |
| deferChunk          | 1.38x     | 1.42x     | 1.87x      | 2.51x       |
| deferSeq           | 1.43x     | 1.51x     | 1.76x      | 2.36x       |
| deferVector        | 1.24x     | 0.82x     | 0.85x      | 1.56x       |
| deferVectorAsSeq   | 2.32x     | 1.90x     | 1.48x      | 2.13x       |
| kyoForeach         | 1.81x     | 1.82x     | 2.01x      | 3.19x       |

(*) from `dotty-cps-async`
author_association: COLLABORATOR
user: login: ahoy-jon
created_at: 2025-05-26T18:02:19Z
body: Benchmark | Size=1024 | Size=65536 | Size=262144 | Size=1048576
-- | -- | -- | -- | --
deferIterable | 1.0x | 1.0x | 1.0x | 1.0x
deferChunk | 1.42x | 1.66x | 1.45x | 2.39x
deferSeq | 1.44x | 1.40x | 1.87x | 2.63x
deferVector | 1.25x | 1.15x | 1.39x | 1.33x
deferVectorAsSeq | 2.33x | 2.00x | 1.53x | 2.00x
kyoForeach | 2.52x | 2.22x | 2.38x | 2.88x


author_association: COLLABORATOR
user: login: ahoy-jon
created_at: 2025-05-26T21:50:45Z
body: @fwbrasil @hearnadam it's ready for review. It's just a bit of performance boost, to catch with `Kyo.foreach` while using `defer`
author_association: COLLABORATOR
user: login: ahoy-jon
created_at: 2025-05-29T00:08:27Z
body: There is still some work to do for the full support:
* ArrayOps
* MapOps
* override all the other operators (flatMap, ...)

So far, `Choice` work with `Vector(1, 2, 3).map` and all the other `Iterable`.

author_association: COLLABORATOR
user: login: ahoy-jon
created_at: 2025-05-29T13:10:57Z
body: @fwbrasil @hearnadam if you could review just to check (`ArrayOps`, `MapOps` are still missing).
author_association: COLLABORATOR
user: login: ahoy-jon
created_at: 2025-05-29T23:07:26Z
body: Done! PR is complete, ready for review!

Note: Removed `MapOps`, and `ArrayOps`, will add support for those later.
author_association: COLLABORATOR