From 4ca493b6614757fccde5a3515227a4a19bf11055 Mon Sep 17 00:00:00 2001 From: Flavio Brasil Date: Thu, 9 Jan 2025 05:50:18 -0800 Subject: [PATCH] [core] introduce Async.apply and clarify the distinction between IO and Async (#986) --- .../shared/src/main/scala/kyo/Async.scala | 41 +++++++++++++++++-- kyo-core/shared/src/main/scala/kyo/IO.scala | 20 +++++---- .../shared/src/test/scala/kyo/AsyncTest.scala | 38 +++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/kyo-core/shared/src/main/scala/kyo/Async.scala b/kyo-core/shared/src/main/scala/kyo/Async.scala index a4091cd81..efc8728ba 100644 --- a/kyo-core/shared/src/main/scala/kyo/Async.scala +++ b/kyo-core/shared/src/main/scala/kyo/Async.scala @@ -9,10 +9,18 @@ import scala.concurrent.Future import scala.util.NotGiven import scala.util.control.NonFatal -/** Represents an asynchronous computation effect. +/** Asynchronous computation effect. * - * This effect provides methods for running asynchronous computations, creating and managing fibers, handling promises, and performing - * parallel and race operations. + * While IO handles pure effect suspension, Async provides the complete toolkit for concurrent programming - managing fibers, scheduling, + * and execution control. It includes IO in its effect set, making it a unified solution for both synchronous and asynchronous operations. + * + * This separation, enabled by Kyo's algebraic effect system, is reflected in the codebase's design: the presence of Async in pending + * effects signals that a computation may park or involve fiber scheduling, contrasting with IO-only operations that run to completion. + * + * Most application code can work exclusively with Async, with the IO/Async distinction becoming relevant primarily in library code or + * performance-critical sections where precise control over execution characteristics is needed. + * + * This effect includes IO in its effect set to handle both async and sync execution in a single effect. * * @see * [[Async.run]] for running asynchronous computations @@ -31,6 +39,33 @@ object Async: sealed trait Join extends ArrowEffect[IOPromise[?, *], Result[Nothing, *]] + /** Convenience method for suspending computations in an Async effect. + * + * While IO is specifically designed to suspend side effects without handling asynchronicity, Async provides both side effect + * suspension and asynchronous execution capabilities (fibers, async scheduling). Since Async includes IO in its effect set, this + * method allows users to work with a single unified effect that handles both concerns. + * + * Note that this method only suspends the computation - it does not fork execution into a new fiber. For concurrent execution, use + * Async.run or combinators like Async.parallel instead. + * + * This is particularly useful in application code where the distinction between pure side effects and asynchronous execution is less + * important than having a simple, consistent way to handle effects. The underlying effects are typically managed together at the + * application boundary through KyoApp. + * + * @param v + * The computation to suspend + * @param frame + * Implicit frame for the computation + * @tparam A + * The result type of the computation + * @tparam S + * Additional effects in the computation + * @return + * The suspended computation wrapped in an Async effect + */ + inline def apply[A, S](inline v: => A < S)(using inline frame: Frame): A < (Async & S) = + IO(v) + /** Runs an asynchronous computation and returns a Fiber. * * @param v diff --git a/kyo-core/shared/src/main/scala/kyo/IO.scala b/kyo-core/shared/src/main/scala/kyo/IO.scala index 921e48dcc..4a7918795 100644 --- a/kyo-core/shared/src/main/scala/kyo/IO.scala +++ b/kyo-core/shared/src/main/scala/kyo/IO.scala @@ -4,15 +4,21 @@ import kyo.Tag import kyo.kernel.* import kyo.kernel.internal.Safepoint -/** Represents an IO effect for handling side effects in a pure functional manner. +/** Pure suspension of side effects. * - * IO allows you to encapsulate and manage side-effecting operations (such as file I/O, network calls, or mutable state modifications) - * within a purely functional context. This enables better reasoning about effects and helps maintain referential transparency. + * Unlike traditional monadic IO types that combine effect suspension and async execution, Kyo leverages algebraic effects to cleanly + * separate these concerns. IO focuses solely on suspending side effects, while async execution (fibers, scheduling) is handled by the + * Async effect. * - * Like Async includes IO, this effect includes Abort[Nothing] to represent potential panics (untracked, unexpected exceptions). IO is - * implemented as a type-level marker rather than a full ArrowEffect for performance. Since Effect.defer is only evaluated by the Pending - * type's "eval" method, which can only handle computations without pending effects, side effects are properly deferred. This ensures they - * can only be executed after an IO.run call, even though it is a purely type-level operation. + * This separation enables an important design principle in Kyo's codebase: methods that only declare IO in their pending effects are run + * to completion without parking or locking. This property, combined with Kyo's lock-free primitives, makes it easier to reason about + * performance characteristics and identify potential async operations in the code. + * + * IO is implemented as a type-level marker rather than a full ArrowEffect for performance. Since Effect.defer is only evaluated by the + * Pending type's "eval" method, which can only handle computations without pending effects, side effects are properly deferred. This + * ensures they can only be executed after an IO.run call, even though it is a purely type-level operation. + * + * Like Async includes IO, this effect includes Abort[Nothing] to represent potential panics (untracked, unexpected exceptions). */ opaque type IO <: Abort[Nothing] = Abort[Nothing] diff --git a/kyo-core/shared/src/test/scala/kyo/AsyncTest.scala b/kyo-core/shared/src/test/scala/kyo/AsyncTest.scala index da5a1c53f..0703c8488 100644 --- a/kyo-core/shared/src/test/scala/kyo/AsyncTest.scala +++ b/kyo-core/shared/src/test/scala/kyo/AsyncTest.scala @@ -1176,4 +1176,42 @@ class AsyncTest extends Test: } } + "apply" - { + "suspends computation" in run { + var counter = 0 + val computation = Async { + counter += 1 + counter + } + for + v1 <- computation + v2 <- computation + v3 <- computation + yield + assert(v1 == 1) + assert(v2 == 2) + assert(v3 == 3) + assert(counter == 3) + end for + } + + "preserves effects" in run { + var executed = false + for + started <- Latch.init(1) + done <- Latch.init(1) + fiber <- Async.run { + started.release.andThen { + Async { executed = true }.andThen { + done.release + } + } + } + _ <- started.await + _ <- done.await + yield assert(executed) + end for + } + } + end AsyncTest