猫效应IO-在使用 /兑换“内部”时,如何确保将异常捕获为值。顶级flatmap?

发布于 2025-02-11 23:31:31 字数 2513 浏览 0 评论 0原文

我试图通过较小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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

霊感 2025-02-18 23:31:31

如果我正确理解您,getEventHandler投掷?

如果是这样,则不会调用.redeem,因为异常会中断。

event =>
  getEventHandler(event) // if this throws
    .redeem(ex => onFailure(ex, event), _ => onSuccess(event)) // this isn't called

因为.flatmap在IO中,捕获了映射在两种情况下都引发的异常。但是只有第二个示例才能赎回它。

您可以通过推迟执行getEventHandler(event)来解决此

event =>
  IO.defer(getEventHandler(event)) // IO.defer catches the exception
    .redeem(ex => onFailure(ex, event), _ => onSuccess(event)) // letting this line handling it

问题 。 f] .defer [a](thunk:=&gt; f [a])。它将您的代码作为by-nam param允许在f中懒洋洋地运行的代码,其中可以捕获例外。

据我所知(可能是错误的),没有CAT效应Typeclass将其作为其定律,将其引入异常应在.map.flatmap < /code>等,但是cats.effect.iomonix.execution.task假定这样做会更安全,因此,如果您要这样投掷这样一个任意的f,您在未指定的行为领域中。 sync [f] .defersync [f] .delay是想到应该处理此类情况的方法,但在将军我仍然会使用它们来避免完全返回f的方法。

If I understood you correctly, getEventHandler throws?

If so then the .redeem won't be called, because exception will interrupt that.

event =>
  getEventHandler(event) // if this throws
    .redeem(ex => onFailure(ex, event), _ => onSuccess(event)) // this isn't called

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)

event =>
  IO.defer(getEventHandler(event)) // IO.defer catches the exception
    .redeem(ex => onFailure(ex, event), _ => onSuccess(event)) // letting this line handling it

If you want this mechanism for any effect, not only cats.effect.IO, look for Sync[F].defer[A](thunk: => F[A]). It takes your code as by-name param letting it be run lazily inside F 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, but cats.effect.IO and monix.execution.Task assumed that it would be safer to do this, so if you are throwing like that for an arbitrary F, you are kind of in the unspecified behavior territory. Sync[F].defer and Sync[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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文