猫效应IO-在使用 /兑换“内部”时,如何确保将异常捕获为值。顶级flatmap?
我试图通过较小iOS组成的程序传递一些数据(事件)。我需要根据事件运行一个计算(可以引发异常),然后报告发生的情况,包括报告中的原始事件。
名义上看起来很简单,但我正在观察出意外的行为(从我看来,这可能很幼稚!)。
我的方法是运行计算,然后使用尝试 /兑换来转换任何抛出的异常。当我使用尝试(或使用尝试使用)“内部” flatmap调用的尝试时,没有捕获异常并崩溃整个应用程序。
如果我将尝试 /兑换在应用程序的“最高级别”上,则应用程序按预期函数 - 将异常捕获并转换为值。
我不确定为什么会发生这种情况。我如何确保可以捕获异常作为值,以便以后可以处理它们?
import cats.effect.{IO, IOApp}
object ParallelExecutionWAttempt extends IOApp.Simple {
def run: IO[Unit] = mainWInnerRedeem
/** Example of a Main program with redeem placed "inside" the flatmap
*
* Expected / desired outcome is for any thrown exception to be captured as a value and handled
*
* What Actually Happens - the exception does not get converted to a value and crashes the whole App
* */
def mainWInnerRedeem: IO[Unit] =
getEventFromSource
.flatMap{
event =>
getEventHandler(event).redeem(ex => onFailure(ex, event), _ => onSuccess(event))
}
/** Main program with redeem as the last in the chain. Result is as expected - the exception is caught.
*
* Unfortunately, pushing to the outside means I can't use the event in the success and failure handlers
*/
def mainWOuterRedeem: IO[Unit] =
getEventFromSource.flatMap(getEventHandler)
.redeem(
ex => IO.println(s"Program Failed exception was $ex"),
_ => IO.println("Program was a Success!")
)
/** Simple Event family for demo */
trait Event
case class Event1(a: Int) extends Event
case class Event2(b: String) extends Event
/** Simple Event Source - constructs an event in an IO */
def getEventFromSource: IO[Event] = IO{Event1(1)}
/** Retrieves a handler for events */
def getEventHandler(event: Event): IO[Unit] = blowsUp(event)
/** Handler funcs for testing - one automatically throws an exception, the other does not */
def blowsUp(event: Event): IO[Unit] = throw new RuntimeException("I blew up!")
def successfulFunc(event: Event): IO[Unit] = IO{println("I don't blow up")}
/** Functions to process handler results - takes event as a param */
def onSuccess(event: Event): IO[Unit] = IO.println(s"Success the event was $event")
def onFailure(throwable: Throwable, event: Event): IO[Unit] = IO.println(s"Failed with $throwable! Event was $event")
}
相关< / strong> - 我注意到,这几乎在任何情况下都会发生在任何上下文中,即尝试 /兑换不在最高级别(即,如果我并行运行两个计算,例如program1.Attempt,program2.AttEmpt)如果要抛出异常,则将崩溃
。 kleislis等
I am attempting to pass some data (an event) through a program composed out of smaller IOs. I need to run a computation (which can throw an exception) based on the event, then report out on what happened including the original event in the report.
This nominally seems straightforward but I'm observing an unexpected behavior (from my view, which may be naive!).
My approach is to run the computation, then use attempt / redeem to convert any thrown exceptions. When I use attempt (or redeem, which uses attempt) "inside" a flatmap call in an IOApp, exceptions are not caught and crash the whole app.
If I put attempt / redeem at the "top level" of the app, the app functions as expected - exceptions are caught and converted to values.
I'm not sure why this is happening. How can I ensure that I can capture exceptions as values so I can handle them later?
import cats.effect.{IO, IOApp}
object ParallelExecutionWAttempt extends IOApp.Simple {
def run: IO[Unit] = mainWInnerRedeem
/** Example of a Main program with redeem placed "inside" the flatmap
*
* Expected / desired outcome is for any thrown exception to be captured as a value and handled
*
* What Actually Happens - the exception does not get converted to a value and crashes the whole App
* */
def mainWInnerRedeem: IO[Unit] =
getEventFromSource
.flatMap{
event =>
getEventHandler(event).redeem(ex => onFailure(ex, event), _ => onSuccess(event))
}
/** Main program with redeem as the last in the chain. Result is as expected - the exception is caught.
*
* Unfortunately, pushing to the outside means I can't use the event in the success and failure handlers
*/
def mainWOuterRedeem: IO[Unit] =
getEventFromSource.flatMap(getEventHandler)
.redeem(
ex => IO.println(s"Program Failed exception was $ex"),
_ => IO.println("Program was a Success!")
)
/** Simple Event family for demo */
trait Event
case class Event1(a: Int) extends Event
case class Event2(b: String) extends Event
/** Simple Event Source - constructs an event in an IO */
def getEventFromSource: IO[Event] = IO{Event1(1)}
/** Retrieves a handler for events */
def getEventHandler(event: Event): IO[Unit] = blowsUp(event)
/** Handler funcs for testing - one automatically throws an exception, the other does not */
def blowsUp(event: Event): IO[Unit] = throw new RuntimeException("I blew up!")
def successfulFunc(event: Event): IO[Unit] = IO{println("I don't blow up")}
/** Functions to process handler results - takes event as a param */
def onSuccess(event: Event): IO[Unit] = IO.println(s"Success the event was $event")
def onFailure(throwable: Throwable, event: Event): IO[Unit] = IO.println(s"Failed with $throwable! Event was $event")
}
Related - I have noticed that this happens in almost any context where the call to attempt / redeem is not at the top level (i.e. if I am running two computations in parallel - e.g. .parTupled(program1.attempt, program2.attempt)
will crash the app if either throws an exception.
Conceptual Note - Yes, there are other ways for me to pass the data through other methods (Reader, Kleislis, etc) those add a bit of overhead for what i'm trying to accomplish here
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
如果我正确理解您,
getEventHandler
投掷?如果是这样,则不会调用
.redeem
,因为异常会中断。因为
.flatmap
在IO中,捕获了映射在两种情况下都引发的异常。但是只有第二个示例才能赎回它。您可以通过推迟执行
getEventHandler(event)
来解决此问题 。 f] .defer [a](thunk:=&gt; f [a])。它将您的代码作为by-nam param允许在
f
中懒洋洋地运行的代码,其中可以捕获例外。据我所知(可能是错误的),没有CAT效应Typeclass将其作为其定律,将其引入
异常
应在.map
或.flatmap < /code>等,但是
cats.effect.io
和monix.execution.task
假定这样做会更安全,因此,如果您要这样投掷这样一个任意的f
,您在未指定的行为领域中。sync [f] .defer
和sync [f] .delay
是想到应该处理此类情况的方法,但在将军我仍然会使用它们来避免完全返回f的方法。If I understood you correctly,
getEventHandler
throws?If so then the
.redeem
won't be called, because exception will interrupt that.Because
.flatMap
in IO catches exceptions thrown by the mapping in both cases exception is caugth by IO. but only the second example can redeem it.You can fix this by deferring execuion of
getEventHandler(event)
If you want this mechanism for any effect, not only
cats.effect.IO
, look forSync[F].defer[A](thunk: => F[A])
. It takes your code as by-name param letting it be run lazily insideF
where exception can be caught.As far as I know (could be wrong), no Cats Effect typeclass has it as its law that thrown
Exception
should be caught in.map
or.flatMap
, etc, butcats.effect.IO
andmonix.execution.Task
assumed that it would be safer to do this, so if you are throwing like that for an arbitraryF
, you are kind of in the unspecified behavior territory.Sync[F].defer
andSync[F].delay
are methods which come to mind as ones that should handle such cases, but in general I would still use them to avoid throwing from methods that should return F altogether.