-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add bridge between ApplicativeError and ApplicativeThrow #4616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
8408933
366d776
e29d1d6
945dfd9
1647c2c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,7 +21,7 @@ | |
|
|
||
| package cats | ||
|
|
||
| import cats.ApplicativeError.CatchOnlyPartiallyApplied | ||
| import cats.ApplicativeError.{CatchOnlyAsPartiallyApplied, CatchOnlyPartiallyApplied} | ||
| import cats.data.{EitherT, Validated} | ||
| import cats.data.Validated.{Invalid, Valid} | ||
|
|
||
|
|
@@ -287,6 +287,30 @@ trait ApplicativeError[F[_], E] extends Applicative[F] { | |
| def catchOnly[T >: Null <: Throwable]: CatchOnlyPartiallyApplied[T, F, E] = | ||
| new CatchOnlyPartiallyApplied[T, F, E](this) | ||
|
|
||
| /** | ||
| * Often E can be created from Throwable. Here we try to call pure or | ||
| * catch, adapt into E, and raise. | ||
| * | ||
| * Exceptions that cannot be adapted to E will be propagated outside of `F` | ||
| */ | ||
| def catchNonFatalAs[A](adaptIfPossible: Throwable => Option[E])(a: => A): F[A] = | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would probably prefer to deal with
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not mentioning that the handler would look more like a regular
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, using PFs for error handling is a common practice 👍🏻
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would expect most times ApplicativeError[Either[E, *], E].catchNonFatalAs(E.fromThrowable) {
// block
}I'm open to being wrong about this, I just expect this sort of transformation won't generally written at the call sites because, if you need to do this, you probably need to do it more than once. |
||
| try pure(a) | ||
| catch { | ||
| case e if NonFatal(e) => adaptIfPossible(e).map(raiseError[A]).getOrElse(throw e) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line concerns me quite a lot, particularly
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless we require the adaptation function to be total, we're going to need to rethrow these, because there isn't a way to raise a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, exactly. This is why we must not use a
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @armanbilge , |
||
| } | ||
|
|
||
| /** | ||
| * Evaluates the specified block, catching exceptions of the specified type and maps them to `E`. | ||
| * | ||
| * Uncaught exceptions will be propagated outside of `F` | ||
| * | ||
| * Note: `catchOnlyAs` assumes that, if a specific exception type `T` is expected, there | ||
| * exists a mapping from `T` to `E`. If this is not the case, consider either manually | ||
| * rethrowing inside the mapping function, or using `catchNonFatalAs` | ||
| */ | ||
| def catchOnlyAs[T >: Null <: Throwable]: CatchOnlyAsPartiallyApplied[T, F, E] = | ||
|
morgen-peschke marked this conversation as resolved.
Outdated
|
||
| new CatchOnlyAsPartiallyApplied[T, F, E](this) | ||
|
|
||
| /** | ||
| * If the error type is Throwable, we can convert from a scala.util.Try | ||
| */ | ||
|
|
@@ -383,6 +407,16 @@ object ApplicativeError { | |
| } | ||
| } | ||
|
|
||
| final private[cats] class CatchOnlyAsPartiallyApplied[T >: Null <: Throwable, F[_], E]( | ||
| private val F: ApplicativeError[F, E] | ||
| ) extends AnyVal { | ||
| def apply[A](adapt: T => E)(f: => A)(implicit CT: ClassTag[T], NT: NotNull[T]): F[A] = | ||
| try F.pure(f) | ||
| catch { | ||
| case CT(t) => F.raiseError(adapt(t)) | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * lift from scala.Option[A] to a F[A] | ||
| * | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Repeating my comments from #4286 (comment):
This doesn't seem right to me. Exception-throwing code should ideally never be written outside of a
catchNonFatal; pure code that uses this method (or any method in Cats) should not throw exceptions.It seems to me what you actually want is maybe something like
F[G[A]], withApplicativeError[G, E]andApplicativeThrow[F]. That way non-adaptable exceptions can be safely handled in theFeffect.In which case I think you want something like this?
Maybe a dedicated method/syntax could make that a bit neater. I'm not entirely sure where it could live.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the extra effect layer would make these incongruent with
catchOnly, which also throws exceptions that aren't expected, rather than attempting to raise them inF.If there were a less clunky way to do the
.map(G.pure).recover { ... }, that would improve the ergonomics enough for the usecase to basically disappear intoSync[F].delay+ whatever the improvement is.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I personally have never used
catchOnlybefore, and never paid attention to it. And now I'm a bit surprised that such a function even exists in here.On the other hand though, there are several examples in Cats when a function can throw an exception, e.g.
NonEmptyList.fromListUnsafeand alike. It is a slightly different case though, because happens not in a type class but rather in a particular data type. But still close.From that point of view, if we want to have functions in Cats like
catchOnlyor similar that can (re)throw exceptions, then perhaps it makes sense to mark them "unsafe", because they really are, e.g.:unsafeCatchOnlyorcatchOnlyUnsafe. And also explain in the docs, why they are unsafe.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would the path forward then be to rename/deprecate the existing
catch*methods?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this PR would be better off staying focused on its initial goal.
Perhaps, it makes sense to provide a safe version of
catchNonFatalAsas @armanbilge suggested along with its unsafe counterpart.The effort on renaming of the existing unsafe methods could be addressed in a separate PR later on. Actually, not all the existing
catch*methods look unsafe to me, onlycatchOnlydoes. On the other hand, not onlyApplicativeErrorcontainscatchOnlybut alsoValidatedandEitherSyntaxhave their specialized versions that can be considered unsafe in the same way. So the renaming is likely worth a separate PR.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured the easiest way to advance would be to implement
*Unsafeversions of the proposed methods, and acatchOnlySafeversion ofcatchOnly.This way we have concrete implementations to look at and figure out which add value.