Skip to content
Open
4 changes: 2 additions & 2 deletions kyo-kernel/shared/src/main/scala/kyo/kernel/ArrowEffect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ object ArrowEffect:
handleLoopLoop(Loop.continue(kyo(v, context)), context)
end new
case kyo =>
kyo.asInstanceOf[A]
kyo.unsafeGet
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice catch!

case _ =>
v.asInstanceOf[A < (S & S2)]
end handleLoopLoop
Expand Down Expand Up @@ -536,7 +536,7 @@ object ArrowEffect:
handleLoopLoop(Loop.continue(state, kyo(v, context)), context)
end new
case kyo =>
done(state, kyo.asInstanceOf[A])
done(state, kyo.unsafeGet)
end match
case _ =>
v.asInstanceOf[B < (S & S2)] // Loop.done
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package kyo.kernel

import kyo.*
import kyo.kernel.*
import kyo.kernel.internal.*

class ArrowEffectNestedTest extends Test:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this belongs in ArrowEffect as a nested Suite?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Let's avoid creating ad-hoc test classes, test files should have corresponding source files.


given [A, B]: CanEqual[A, B] = CanEqual.derived

sealed trait TestEffect extends ArrowEffect[Const[Int], Const[Int]]

def suspend(i: Int): Int < TestEffect =
ArrowEffect.suspend[Any](Tag[TestEffect], i)

val tag: Tag[TestEffect] = Tag[TestEffect]

def flatten[A, B, C](v: A < B < C): A < (B & C) = v.map(a => a)

"handle on Nested (unsafeGet path)" - {

def handle[A, S](v: A < (S & TestEffect)): A < S =
ArrowEffect.handle(tag, v):
[C] => (input, cont) => cont(input * 10)

"unwraps Nested and returns inner suspension" in {
val comp: Int < TestEffect = suspend(5)
val nested: Int < TestEffect < Any = Nested(comp)
val result: Int < TestEffect < Any = handle(nested)

assert(result == nested, "handleSimple should return the nested computation")

val flattened = flatten(result)
val finalResult = handle(flattened)

assert(finalResult.eval == 50)
}
}

"handleFirst on Nested" - {

def handle[A, S](v: A < (S & TestEffect)): A < (S & TestEffect) =
ArrowEffect.handleFirst(tag, v)(
[C] => (input, cont) => cont(input * 10),
identity
)

"done callback receives unwrapped value" in {
val comp = suspend(5)
val nested: Int < TestEffect < Any = Nested(comp)

val result = handle(nested)

assert(result == nested, "handleFirst should return the nested computation")

val flattened = flatten(result)
val finalResult: Int < TestEffect = handle(flattened)
assert(finalResult.evalNow == Maybe(50))
}
}

"handleLoop (stateless) on Nested" - {

def handle[A, S](v: A < (S & TestEffect)): A < S =
ArrowEffect.handleLoop(Tag[TestEffect], v):
[C] => (input, cont) => Loop.continue(cont(input * 10))

"unwraps Nested and handles inner suspension" in {
val comp: Int < TestEffect = suspend(5)
val nested: Int < TestEffect < Any = Nested(comp)

val result = handle(nested)
assert(result == nested, "handleLoop should return the nested computation")

val flattened = flatten(result)
val finalResult: Int < Any = handle(flattened)

assert(finalResult.eval == 50)
}
}

"handleLoop (stateful) on Nested" - {

def handle[A, S](v: A < (S & TestEffect)): A < S =
ArrowEffect.handleLoop(tag, 0, v)(
[C] => (input, state, cont) => Loop.continue(state + 1, cont((input + state) * 10))
)

"unwraps Nested and handles inner suspension" in {
val comp: Int < TestEffect = suspend(5)
val nested: Int < TestEffect < Any = Nested(comp)

val result = handle(nested)
assert(result == nested, "handleLoop should return the nested computation")

val flattened = flatten(result)
val finalResult: Int < Any = handle(flattened)

assert(finalResult.eval == 50)
}

}

"handleLoop (stateful + done) on Nested" - {

def handle[A, S](v: A < (S & TestEffect)): A < S =
ArrowEffect.handleLoop(tag, 0, v)(
[C] => (input, state, cont) => Loop.continue(state + 1, cont(input * 10)),
(state, v) => v
)

"unwraps Nested and handles inner suspension" in {
val comp: Int < TestEffect = suspend(5)
val nested: Int < TestEffect < Any = Nested(comp)

val result = handle(nested)
assert(result == nested, "handleLoop should return the nested computation")

val flattened = flatten(result)
val finalResult: Int < Any = handle(flattened)

assert(finalResult.eval == 50)
}
}

"handleCatching on Nested" - {

def handle[A, S](v: A < (S & TestEffect)): A < S =
ArrowEffect.handleCatching(tag, v)(
[C] => (input, cont) => cont(input * 10),
recover = e => throw e
)

"unwraps Nested and handles inner suspension" in {
val comp: Int < TestEffect = suspend(5)
val nested: Int < TestEffect < Any = Nested(comp)

val result = handle(nested)
assert(result == nested, "handleLoop should return the nested computation")

val flattened = flatten(result)
val finalResult: Int < Any = handle(flattened)

assert(finalResult.eval == 50)
}
}

"handlePartial on Nested" - {

def handle[A, S](v: A < (S & TestEffect)): A < (S & TestEffect) =
ArrowEffect.handlePartial(tag, tag, v, Context.empty)(
stop =
false,
[C] => (input, cont) => cont(input * 10),
[C] => (input, cont) => cont(input * 10)
)

"unwraps Nested and handles inner suspension" in {
val comp: Int < TestEffect = suspend(5)
val nested: Int < TestEffect < Any = Nested(comp)

val result = handle(nested)
assert(result == nested, "handlePartial should return the nested computation")

val flattened = flatten(result)
val finalResult = handle(flattened)
assert(finalResult.evalNow == Maybe(50))
}
}

end ArrowEffectNestedTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package kyo.kernel.internal

import kyo.*

class NestedHandlerTest extends Test:
"bug #1412 - handler on Nested" - {
"Var.update through lift/flatten/run" in {
var executed = false

val comp: Unit < Var[Int] = Var.update[Int] { x =>
executed = true
x + 1
}.unit

val lifted: Unit < Var[Int] < Any = Nested(comp) // Kyo.lift(comp)

def flatten[A, B, C](v: A < B < C): A < (B & C) = v.map(a => a)

val step1 = Var.run(0)(lifted)
val step2 = flatten(step1)
val step3 = Var.run(0)(step2)

step3.eval
assert(executed, "Var.update body should have been executed")
}

"details" in {
given [A, B]: CanEqual[A, B] = CanEqual.derived

val update: Int < Var[Int] = Var.update[Int](_ + 1)
val nested: Int < Var[Int] < Any = Nested(update)
val handled: Int < Var[Int] < Any = Var.run(0)(nested)

assert(handled == nested, "Var.run should return the nested computation")
}
}

end NestedHandlerTest
Loading