8

During a PR review, I was asked to replace Sync[F].delay with Sync[F].catchNonFatal because an exception might be thrown.

This does work:

scala> Sync[IO].delay(throw new Exception).recover{ case t: Throwable => 42 }.unsafeRunSync
res10: Int = 42

Not being sure if that behavior was specific to IO, I was also able to find the corresponding law saying it's actually expected, but I could not find mentions in the main cats-effect documentation about automatic handling of exceptions in the API.

Does anybody know the rationale and the expected behavior followed by cats-effect w.r.t. exceptions thrown in .delay or .map or .flatMap?

betehess
  • 839
  • 4
  • 19
  • 2
    I'd take the laws as authoritative here, which means `delay` and `suspend` must catch ambient exceptions, while `map` and `flatMap` may or may not, depending on the instance (and whether they do or not for any given instance is likely only to be documented in some GitHub issue somewhere, unfortunately). – Travis Brown Feb 13 '19 at 20:17
  • 2
    …anyway your reviewer was wrong. There are reasons you might prefer `catchNonFatal`, but they have to do with when evaluation happens, not how exceptions are handled. – Travis Brown Feb 13 '19 at 20:20

1 Answers1

0

As you discovered, it's guaranteed by the laws that delay catches exceptions - that's an important part of the API and it would be very hard to use if that weren't true.

As for map and flatMap, there are no law-based guarantees that exceptions are caught in those methods - those methods are defined in the Functor and FlatMap typeclasses, not cats-effect. They expect pure functions, and catching thrown exceptions isn't referentially transparent.

However in practice, for IO specifically, exceptions are caught as part of the IO type's contract. But it's never ideal to rely on it for generic code.

So, don't write code like

IO(blah).map(a => mightThrow(a))

Instead write

IO(blah).flatMap(a => Either.catchNonFatal(mightThrow(a)).liftTo[IO])

Ideally the mightThrow method would return an Either instead of throwing, if it's otherwise not side-effecting

Daenyth
  • 35,856
  • 13
  • 85
  • 124