-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Reimplemented MonadError[FreeT[...]] to be correct #3241
Conversation
override def M = E | ||
|
||
/* | ||
* Quick explanation... The previous version of this function (retained about for |
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.
typo: ...retained **above** for...
Is it possible to adjust the generators / equalities used in tests to verify that the old implementation does indeed break a law? |
Codecov Report
@@ Coverage Diff @@
## master #3241 +/- ##
==========================================
+ Coverage 93.05% 93.08% +0.02%
==========================================
Files 376 376
Lines 7413 7428 +15
Branches 201 196 -5
==========================================
+ Hits 6898 6914 +16
+ Misses 515 514 -1
Continue to review full report at Codecov.
|
Thanks for spotting and fixing this bug. |
@kubukoz I've been wondering the same thing myself. I tried fiddling around with some sort of distributivity law for @kailuowang See above. :-) I want to add a law. Thoughts welcome. My attempt was something like this: def handleErrorWithDistributesOverAp[A, E](fa1: F[A], fa2: F[A], h: E => F[A]): F[A] =
F.handleErrorWith(fa1 *> fa2)(h) <-> (F.handleErrorWith(fa1)(h) *> F.handleErrorWith(fa2)(h)) That's not actually right, though, because
The constraints are different. The new requires a
Actually it has to be public. The problem is the static forwarder. I've fixed it now. |
Thanks @djspiewak I missed the new constraint. This is source breaking then, I am adding the label just so that it doesn't surprise users. |
How about this as a law? def attemptDistributivity[A, B](fa: F[A], f: A => F[B]): F[Either[E, B]] =
F.attempt(F.flatMap(fa)(f)) <-> F.flatMap(F.attempt(fa))(_.traverse(f.andThen(F.attempt))) |
(btw, please don't merge this just yet; even aside from the discussion about a new law, which is important, I have some ancillary evidence that this isn't appropriately stack-safe. |
@kailuowang @kubukoz Okay, I've added a pair of laws in ApplicativeError which capture this case. I've verified that they do indeed fail on I've also added a Edit: Also to be clear, feel free to merge once everyone approves, particularly the new laws. I'm not aware of any further changes that are necessary, excepting what I'll need to change in response to review. |
/* | ||
* These laws, taken together with applicativeErrorHandle, show that errors dominate in | ||
* ap, *and* show that handle has lexical semantics over ap. F.unit is used in both laws | ||
* because we don't have another way of expressing "an F[_] which does *not* contain any |
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.
Another way might be Arbitrary[F[_]].map(_.attempt)
... But that's probably problematic in other ways (depending on the behavior of attempt)
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.
Well, the problem is then you need a function E => A
. That's not doable without breaking binary compatibility.
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.
Why binary compatibility? I fail to see that here...
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.
In order to have an Arbitrary[E => A]
, we need a Cogen[E]
and an Arbitrary[A]
. We have the former, but not the latter (in ApplicativeErrorTests
). In order to get the latter, we would need to add it as a parameter to the applicativeError
function, which breaks binary compatibility.
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.
Ohhh. Yeah, you're absolutely right.
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.
Thanks very much!
There's an explanation of this in the comments in the code. I retained the old implementation for binary compatibility, but it is somewhat unfortunate since any code which is hitting that is outright-broken and people may not know it. Then again, I'm not sure how many people (other than me) are using
FreeT
, so maybe it's okay either way.Fixes #3240