Skip to content

Commit

Permalink
core: require AllowUnsafe in KyoApp.* methods (#748)
Browse files Browse the repository at this point in the history
  • Loading branch information
fwbrasil authored Oct 15, 2024
1 parent 354267d commit afdb947
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 96 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -442,11 +442,13 @@ val a: Int < IO =

// Avoid! Run the application with a specific timeout
val b: Int =
KyoApp.run(2.minutes)(a)
import AllowUnsafe.embrace.danger
KyoApp.Unsafe.run(2.minutes)(a)

// Avoid! Run the application without specifying a timeout
val c: Int =
KyoApp.run(a)
import AllowUnsafe.embrace.danger
KyoApp.Unsafe.run(a)
```

## Core Effects
Expand Down
188 changes: 96 additions & 92 deletions kyo-core/shared/src/main/scala/kyo/KyoApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ abstract class KyoApp extends KyoApp.Base[KyoApp.Effects]:
def clock: Clock = Clock.live

override protected def handle[A: Flat](v: A < KyoApp.Effects)(using Frame): Unit =
import AllowUnsafe.embrace.danger
v.map { v =>
if (()).equals(v) then ()
else Console.println(v)
}.pipe(
Clock.let(clock),
Random.let(random),
Log.let(log),
KyoApp.run
KyoApp.Unsafe.run
)
end handle
end KyoApp

object KyoApp:
Expand Down Expand Up @@ -56,96 +58,98 @@ object KyoApp:
/** The combined effect type used by KyoApp. */
type Effects = Async & Resource & Abort[Throwable]

/** Attempts to run an effect with a specified timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param timeout
* The maximum duration to wait for the effect to complete.
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* A Result containing either the computed value or a Throwable.
*/
def attempt[A: Flat](timeout: Duration)(v: A < Effects)(using Frame): Result[Throwable, A] =
import AllowUnsafe.embrace.danger
IO.Unsafe.run(runFiber(timeout)(v).block(timeout)).eval

/** Runs an effect with a specified timeout, throwing an exception if it fails.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param timeout
* The maximum duration to wait for the effect to complete.
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* The computed value of type A.
* @throws Throwable
* if the effect fails or times out.
*/
def run[A: Flat](timeout: Duration)(v: A < Effects)(using Frame): A =
attempt(timeout)(v).getOrThrow

/** Runs an effect with an infinite timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* The computed value of type A.
* @throws Throwable
* if the effect fails.
*/
def run[A: Flat](v: A < Effects)(using Frame): A =
run(Duration.Infinity)(v)

/** Creates a Fiber to run an effect with an infinite timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* A Fiber representing the running effect.
*/
def runFiber[A: Flat](v: A < Effects)(using Frame): Fiber[Throwable, A] =
runFiber(Duration.Infinity)(v)

/** Creates a Fiber to run an effect with a specified timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param timeout
* The maximum duration to wait for the effect to complete.
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* A Fiber representing the running effect.
*/
def runFiber[A: Flat](timeout: Duration)(v: A < Effects)(using Frame): Fiber[Throwable, A] =
import AllowUnsafe.embrace.danger
v.pipe(Resource.run, Async.run, IO.Unsafe.run).eval
/** WARNING: Low-level API meant for integrations, libraries, and performance-sensitive code. See AllowUnsafe for more details. */
object Unsafe:

/** Attempts to run an effect with a specified timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param timeout
* The maximum duration to wait for the effect to complete.
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* A Result containing either the computed value or a Throwable.
*/
def attempt[A: Flat](timeout: Duration)(v: A < Effects)(using Frame, AllowUnsafe): Result[Throwable, A] =
IO.Unsafe.run(runFiber(timeout)(v).block(timeout)).eval

/** Runs an effect with a specified timeout, throwing an exception if it fails.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param timeout
* The maximum duration to wait for the effect to complete.
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* The computed value of type A.
* @throws Throwable
* if the effect fails or times out.
*/
def run[A: Flat](timeout: Duration)(v: A < Effects)(using Frame, AllowUnsafe): A =
attempt(timeout)(v).getOrThrow

/** Runs an effect with an infinite timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* The computed value of type A.
* @throws Throwable
* if the effect fails.
*/
def run[A: Flat](v: A < Effects)(using Frame, AllowUnsafe): A =
run(Duration.Infinity)(v)

/** Creates a Fiber to run an effect with an infinite timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* A Fiber representing the running effect.
*/
def runFiber[A: Flat](v: A < Effects)(using Frame, AllowUnsafe): Fiber[Throwable, A] =
runFiber(Duration.Infinity)(v)

/** Creates a Fiber to run an effect with a specified timeout.
*
* Note: This method is unsafe and should only be used as the entrypoint of an application.
*
* @param timeout
* The maximum duration to wait for the effect to complete.
* @param v
* The effect to run.
* @param ev
* Evidence that A is Flat.
* @param frame
* The implicit Frame.
* @return
* A Fiber representing the running effect.
*/
def runFiber[A: Flat](timeout: Duration)(v: A < Effects)(using Frame, AllowUnsafe): Fiber[Throwable, A] =
v.pipe(Resource.run, Async.timeout(timeout), Async.run, IO.Unsafe.run).eval
end Unsafe

end KyoApp
6 changes: 4 additions & 2 deletions kyo-core/shared/src/test/scala/kyo/KyoAppTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class KyoAppTest extends Test:
_ <- Async.run(())
yield 1

assert(KyoApp.run(Duration.Infinity)(run) == 1)
import AllowUnsafe.embrace.danger
assert(KyoApp.Unsafe.run(Duration.Infinity)(run) == 1)
}
"failing effects" taggedAs jvmOnly in {
def run: Unit < KyoApp.Effects =
Expand All @@ -51,7 +52,8 @@ class KyoAppTest extends Test:
_ <- Abort.fail(new RuntimeException("Aborts!"))
yield ()

KyoApp.attempt(Duration.Infinity)(run) match
import AllowUnsafe.embrace.danger
KyoApp.Unsafe.attempt(Duration.Infinity)(run) match
case Result.Fail(exception) => assert(exception.getMessage == "Aborts!")
case _ => fail("Unexpected Success...")
}
Expand Down

0 comments on commit afdb947

Please sign in to comment.