Skip to content
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

Add onError and adaptError to Applicative/MonadError #1739

Merged
merged 4 commits into from
Jul 24, 2017

Conversation

SystemFw
Copy link
Contributor

PR:
As per this Gitter conversation with @djspiewak, this PR adds two functions:

ApplicativeError#onError, to execute a callback on certain errors and rethrow:

onError[A](fa: F[A])(pf: PartialFunction[E, F[Unit]]): F[A]

with laws:

pure(a).onError(f) <-> pure(a)
raiseError(e).onError(err => fb) <-> fb *> raiseError(e)

Use case: error handling that respects module boundaries, e.g. rollback at the db layer, and then send a failed response at the http layer

MonadError#adaptError, to transform certain errors

def adaptError[A](fa: F[A])(pf: PartialFunction[E,E]): F[A] 

with laws:

pure(a).adaptError(f) <-> pure(a)
raiseError(e).adaptError(f) <-> raiseError(f(e))

Use case: transforming errors (especially Throwable) between different layers of an application. The type is not as good as leftMap but the best we can have I think (except maybe constraining to Throwable).

A few things I'm not sure about code-wise:

  • Lack of syntax imports and trying to adhere to the existing style resulted in fairly "lispy" code
  • I couldn't get an Arbitrary[F[Unit]] in the laws, had to settle for F[B].void
  • Haven't added non-discipline tests (yet?), they all seem to be based on Option, for which none of these two functions happen to make sense
  • Should I add specialised implementations where relevant?

@kailuowang
Copy link
Contributor

assigning @edmundnoble as well since he is removing ApplicativeError

@codecov-io
Copy link

codecov-io commented Jun 21, 2017

Codecov Report

Merging #1739 into master will increase coverage by 1.27%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1739      +/-   ##
==========================================
+ Coverage      94%   95.27%   +1.27%     
==========================================
  Files         253      265      +12     
  Lines        4171     4298     +127     
  Branches      162       99      -63     
==========================================
+ Hits         3921     4095     +174     
+ Misses        250      203      -47
Impacted Files Coverage Δ
core/src/main/scala/cats/MonadError.scala 100% <100%> (ø) ⬆️
.../src/main/scala/cats/syntax/applicativeError.scala 100% <100%> (ø) ⬆️
...a/cats/laws/discipline/ApplicativeErrorTests.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/syntax/monadError.scala 100% <100%> (ø) ⬆️
...rc/main/scala/cats/laws/ApplicativeErrorLaws.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/ApplicativeError.scala 88.23% <100%> (+1.56%) ⬆️
laws/src/main/scala/cats/laws/MonadErrorLaws.scala 100% <100%> (ø) ⬆️
...n/scala/cats/laws/discipline/MonadErrorTests.scala 100% <100%> (ø) ⬆️
core/src/main/scala/cats/syntax/cartesian.scala 50% <0%> (-25%) ⬇️
core/src/main/scala/cats/instances/map.scala 94.44% <0%> (-0.16%) ⬇️
... and 52 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4ba557c...c6223f3. Read the comment docs.

@SystemFw
Copy link
Contributor Author

SystemFw commented Jun 30, 2017

I'm waiting for #1751 to be resolved to update this PR (probably just moving stuff to MonadError)

@@ -34,4 +34,7 @@ final class ApplicativeErrorOps[F[_], E, A](val fa: F[A]) extends AnyVal {

def recoverWith(pf: PartialFunction[E, F[A]])(implicit F: ApplicativeError[F, E]): F[A] =
F.recoverWith(fa)(pf)

def onError(fa: F[A])(pf: PartialFunction[E, F[Unit]])(implicit F: ApplicativeError[F, E]): F[A] =
F.onError(fa)(pf)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is untested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I mentioned this in the description :)

Haven't added non-discipline tests (yet?), they all seem to be based on Option, for which none of these two functions happen to make sense

Should I add a more complicated transformer to the tests to test this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm, since this is a simple delegation what about adding a simple doctest (maybe using Either) to the method in the type class?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bear in mind that tests are missing for the implementation, not just for the syntax. I have added laws and discipline tests, but the reason I still haven't added unit tests is that all the unit tests I could find are based on Option, whereas testing this would require something like
EitherT[Writer, ...., I don't have a problem with it, just wanting to know if it's a good idea

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assumed that the discipline tests provides enough coverage for the implementation, no? Anyway I am not against adding a EitherT[Writer,... to the unit tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, my idea was exactly that discipline tests might be enough, which is way I submitted this in its current form. Looking for advice from the maintainers to take the final decision

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, I think @johnynek and I were saying that ideally we also want to cover the delegation in the syntax, and one way to do that easily is to add a doctest in the type class method declaration, which uses the syntax and can also serve as an example in the scala doc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't see any doctests for the other methods on ApplicativeError, apart from a scaladoc example for fromEither. Would you happen to have an example at hand of how syntax forwarders are tested in cats via doctests ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add these today, especially since there's a bug 😁 (the fa should not be a parameter, shadows the implicit class parameter)

@@ -12,4 +12,7 @@ final class MonadErrorOps[F[_], E, A](val fa: F[A]) extends AnyVal {

def ensureOr(error: A => E)(predicate: A => Boolean)(implicit F: MonadError[F, E]): F[A] =
F.ensureOr(fa)(error)(predicate)

def adaptError(fa: F[A])(pf: PartialFunction[E, E])(implicit F: MonadError[F, E]): F[A] =
F.adaptError(fa)(pf)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is untested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

@SystemFw
Copy link
Contributor Author

@edmundnoble @kailuowang @djspiewak Any news on this one? I'm still looking for advice on the testing, but apart from that, this would be nice to have for use with cats-effect IO, now that ApplicativeError seems to be staying in cats.

@@ -39,6 +39,12 @@ trait ApplicativeErrorLaws[F[_], E] extends ApplicativeLaws[F] {

def attemptFromEitherConsistentWithPure[A](eab: Either[E, A]): IsEq[F[Either[E, A]]] =
F.attempt(F.fromEither(eab)) <-> F.pure(eab)

def onErrorPure[A, B](a: A, f: E => F[B]): IsEq[F[A]] =
F.onError(F.pure(a)){case err => F.void(f(err))} <-> F.pure(a)
Copy link
Contributor

@kailuowang kailuowang Jul 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite sure I follow here, why not def onErrorPure[A, B](a: A, f: E => F[Unit])....? why take a E => F[B]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mentioned this in my comments, I couldn't get a Gen[F[Unit]]. However, it was my first time dealing with all the discipline infrastructure, and never had to to this particular thing in my own experience with scalacheck, so glad to be proven wrong :)

Copy link
Contributor

@kailuowang kailuowang Jul 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adding an ArbFU: Arbitrary[F[Unit]] param to the MonadErrorTests and ApplicativeErrorTest methods should do the trick

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is exactly what I did and iirc it didn't compile. Let me give it another try though :)

Copy link
Contributor

@kailuowang kailuowang Jul 20, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compiled for me, EitherTests passes. running full now. update: full suite passed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you're right. I hadn't realised I needed to add it to the MonadErrorTests as well (because of parent). Thank you! (I'll update the PR tonight with the change)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are welcome. Thank you for contributing!

@kailuowang
Copy link
Contributor

Also I think #1751 is close to merge. I think we can safely assume that ApplicativeError is going to stay.

@SystemFw
Copy link
Contributor Author

@kailuowang I've simplified the discipline tests to take F[Unit], and added doctests for onError and applicativeError, let me know if there's any more feedback

@kailuowang
Copy link
Contributor

👍 LGTM, thanks very much @SystemFw

@edmundnoble
Copy link
Contributor

Merging with two approvals.

@edmundnoble edmundnoble merged commit 8571489 into typelevel:master Jul 24, 2017
@kailuowang kailuowang added this to the 1.0.0-MF milestone Jul 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants