-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
I wanted to avoid this integration because of past issues with Typelevel, but I think it's best if we get ahead of a possible integration and ensure it's under our control in the repository.
- Loading branch information
Showing
5 changed files
with
265 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package kyo | ||
|
||
import Cats.GetCatsIO | ||
import cats.effect.IO as CatsIO | ||
import kyo.kernel.* | ||
import scala.util.control.NonFatal | ||
|
||
opaque type Cats <: (Async & Abort[Throwable]) = GetCatsIO & Async & Abort[Throwable] | ||
|
||
object Cats: | ||
|
||
/** Lifts a cats.effect.IO into a Kyo effect. | ||
* | ||
* @param io | ||
* The cats.effect.IO to lift | ||
* @return | ||
* A Kyo effect that, when run, will execute the cats.effect.IO | ||
*/ | ||
def get[A](io: CatsIO[A])(using Frame): A < Cats = | ||
ArrowEffect.suspendMap(Tag[GetCatsIO], io)(Abort.get(_)) | ||
|
||
/** Runs a Kyo effect that uses Cats and converts it to a cats.effect.IO. | ||
* | ||
* @param v | ||
* The Kyo effect to run | ||
* @return | ||
* A cats.effect.IO that, when run, will execute the Kyo effect | ||
*/ | ||
def run[A](v: => A < Cats)(using frame: Frame): CatsIO[A] = | ||
CatsIO.defer { | ||
ArrowEffect.handle(Tag[GetCatsIO], v.map(CatsIO.pure))( | ||
[C] => (input, cont) => input.attempt.flatMap(r => run(cont(r)).flatten) | ||
).pipe(Async.run) | ||
.map { fiber => | ||
CatsIO.async[CatsIO[A]] { cb => | ||
CatsIO { | ||
fiber.unsafe.onComplete(r => cb(r.toEither)) | ||
Some(CatsIO(fiber.unsafe.interrupt(Result.Panic(Fiber.Interrupted(frame)))).void) | ||
} | ||
} | ||
}.pipe(IO.run).eval.flatten | ||
} | ||
end run | ||
|
||
sealed private[kyo] trait GetCatsIO extends ArrowEffect[CatsIO, Either[Throwable, *]] | ||
end Cats |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package kyo | ||
|
||
import cats.effect.IO as CatsIO | ||
import cats.effect.kernel.Fiber as CatsFiber | ||
import cats.effect.kernel.Outcome | ||
import cats.effect.unsafe.implicits.global | ||
import kyo.* | ||
import kyo.kernel.Platform | ||
import org.scalatest.compatible.Assertion | ||
import org.scalatest.concurrent.Eventually.* | ||
import scala.concurrent.Future | ||
import scala.concurrent.duration.* | ||
|
||
class CatsTest extends Test: | ||
|
||
def runCatsIO[T](v: CatsIO[T]): Future[T] = | ||
v.unsafeToFuture() | ||
|
||
def runKyo(v: => Assertion < (Abort[Throwable] & Cats)): Future[Assertion] = | ||
Cats.run(v).unsafeToFuture() | ||
|
||
"Abort ordering" - { | ||
"kyo then cats" in runKyo { | ||
object catsFailure extends RuntimeException | ||
object kyoFailure extends RuntimeException | ||
val a = Abort.fail(kyoFailure) | ||
val b = Cats.get(CatsIO.raiseError(catsFailure)) | ||
Abort.run[Throwable](a.map(_ => b)).map { | ||
case Result.Fail(ex) => | ||
assert(ex == kyoFailure) | ||
case _ => | ||
fail() | ||
} | ||
} | ||
"cats then kyo" in runKyo { | ||
object catsFailure extends RuntimeException | ||
object kyoFailure extends RuntimeException | ||
val a = Cats.get(CatsIO.raiseError(catsFailure)) | ||
val b = Abort.fail(kyoFailure) | ||
Abort.run[Throwable](a.map(_ => b)).map { | ||
case Result.Fail(ex) => | ||
assert(ex == catsFailure) | ||
case ex => | ||
fail() | ||
} | ||
} | ||
} | ||
|
||
"A < Cats" in runKyo { | ||
val a = Cats.get(CatsIO.pure(10)) | ||
val b = a.map(_ * 2) | ||
b.map(i => assert(i == 20)) | ||
} | ||
|
||
"nested" in runKyo { | ||
val a = Cats.get(CatsIO.pure(Cats.get(CatsIO.pure("Nested")))).flatten | ||
a.map(s => assert(s == "Nested")) | ||
} | ||
|
||
"fibers" in runKyo { | ||
for | ||
v1 <- Cats.get(CatsIO.pure(1)) | ||
v2 <- Async.run(2).map(_.get) | ||
v3 <- Cats.get(CatsIO.pure(3)) | ||
yield assert(v1 == 1 && v2 == 2 && v3 == 3) | ||
} | ||
|
||
"interrupts" - { | ||
|
||
import java.util.concurrent.atomic.LongAdder | ||
|
||
def kyoLoop(a: LongAdder = new LongAdder): Unit < IO = | ||
IO(a.increment()).map(_ => kyoLoop(a)) | ||
|
||
def catsLoop(a: LongAdder = new LongAdder): CatsIO[Unit] = | ||
CatsIO.delay(a.increment()).flatMap(_ => catsLoop(a)) | ||
|
||
if Platform.isJVM then | ||
|
||
"cats to kyo" in runCatsIO { | ||
for | ||
f <- Cats.run(kyoLoop()).start | ||
_ <- f.cancel | ||
r <- f.join | ||
yield assert(r.isCanceled) | ||
end for | ||
} | ||
"kyo to cats" in runKyo { | ||
for | ||
f <- Async.run(Cats.run(catsLoop())) | ||
_ <- f.interrupt(Result.Panic(new Exception)) | ||
r <- f.getResult | ||
yield assert(r.isPanic) | ||
end for | ||
} | ||
"both" in runCatsIO { | ||
val v = | ||
for | ||
_ <- Cats.get(catsLoop()) | ||
_ <- Async.run(kyoLoop()) | ||
yield () | ||
for | ||
f <- Cats.run(v).start | ||
_ <- f.cancel | ||
r <- f.join | ||
yield assert(r.isCanceled) | ||
end for | ||
} | ||
end if | ||
} | ||
|
||
"Error handling" - { | ||
"Kyo Abort to Cats IO error" in runKyo { | ||
val kyoAbort = Abort.fail(new Exception("Kyo error")) | ||
val converted = Cats.get(CatsIO.fromEither(Abort.run(kyoAbort).eval.toEither)) | ||
Abort.run[Throwable](converted).map { | ||
case Result.Fail(ex) => assert(ex.getMessage() == "Kyo error") | ||
case _ => fail("Expected a String error") | ||
} | ||
} | ||
|
||
"Cats IO error to Kyo Abort" in runKyo { | ||
val catsError = CatsIO.raiseError[Int](new Exception("Cats error")) | ||
val converted = Cats.get(catsError) | ||
Abort.run[Throwable](converted).map { | ||
case Result.Fail(error: Exception) => assert(error.getMessage == "Cats error") | ||
case _ => fail("Expected an Exception") | ||
} | ||
} | ||
} | ||
|
||
end CatsTest |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package kyo | ||
|
||
import kyo.internal.BaseKyoTest | ||
import kyo.kernel.Platform | ||
import org.scalatest.NonImplicitAssertions | ||
import org.scalatest.freespec.AsyncFreeSpec | ||
import scala.concurrent.ExecutionContext | ||
import scala.concurrent.Future | ||
|
||
abstract class Test extends AsyncFreeSpec with BaseKyoTest[Any] with NonImplicitAssertions: | ||
|
||
def run(v: Future[Assertion] < Any): Future[Assertion] = v.eval | ||
|
||
type Assertion = org.scalatest.compatible.Assertion | ||
def success = succeed | ||
|
||
override given executionContext: ExecutionContext = Platform.executionContext | ||
end Test |