-
Notifications
You must be signed in to change notification settings - Fork 532
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
onCancel + continual #519
onCancel + continual #519
Conversation
/** | ||
* This is the default [[Concurrent.continual]] implementation. | ||
*/ | ||
def continual[F[_], A, B](fa: F[A])(f: Either[Throwable, A] => F[B]) |
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.
Is there a more efficient implementation of this for IO
, or other concrete datatypes?
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.
There are all sorts of things you can do if you allow internal changes, at various levels of ambition. If all you want is continual
, all you need to do is disable the the interrupt check on flatMap
(continual
is equivalent to our previous model without interruptible flatMaps, in which case it's equivalent to attempt.flatMap
)
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.
We could implement it more efficiently, yes. It should eventually end up on one of our traits.
Note that Cats 2.0 is not breaking binary compatibility on 2.11. Are we all comfortable breaking with their precedent there? I lean toward yes, but I want to highlight that, since we're otherwise mirroring their approach. |
I think we could avoid breaking bincompat here if we only add these operations to the syntax via bincompat traits, but then users won't be able to override them. It's not important for In any case it was my understanding we were going to break bincompat for 2.0, but happy to discuss :) |
* restore the state to its previous value. | ||
* | ||
* {{{ | ||
* waitingOp.onCancel(_ => restoreState) |
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 _ =>
?
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, will fix, cheers
We could split this into something that works on series/1.x either by just having the function on the Continual companion, or using a bincompat trait (which can't be overridden) to get the syntax we eventually want on the typeclass. A similar technique is possible for We haven't firmly decided whether to break bincompat in 2.0 or not outside of laws. Semver says we can, alignment to cats precedent says not. If we do, it would be nice to fix a couple other major annoyances and push 3.0 deeper into the future. My inclination is to either fix our biggest warts in 2.0 or to keep 2.0 compatible. The ecosystem is going to need a cats-effect-2.0 quickly after cats-2.0, so there is some time pressure. |
@rossabaker My view: I'm okay with breaking bincompat in 2.0, but only very minorly. That's a sliding scale to be sure, but I think one that most users would be okay with. For example, breaking compatibility due to adding concrete trait functions (which only breaks 2.11) is okay in my book. Cleaning up any implicit shimming (which we probably don't have, as you referenced the other night) is also okay. Basically if it maintains source compatibility, it's definitely okay in my book. That's just my opinion. Open to being convinced. |
The difference between breaking binary compatibility and not in core the difference between that downstream projects should rerelease (or have their Gitters flooded with users asking about eviction warnings) or that they must. Source compatibility is a worthy goal to lessen this burden if we impose it. So, yeah, I'm 👍 on this PR for 2.x. The outstanding question is whether any pieces should be brought back to 1.3. |
I agree with @djspiewak characterisation wrt bincompat in 2.0
How long are we talking about for 2.0? if it's a matter of weeks it might be not worth doing all the BinCompat trait dance, since both of these things are achievable in user code. If it's a matter of months then maybe it is |
Yes, a matter of weeks. |
* use [[Concurrent$.continual Concurrent.continual]] in the | ||
* companion object. | ||
*/ | ||
def continual[A, B](fa: F[A])(f: Either[Throwable, A] => F[B]): F[B] = |
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.
This obviously breaks binary compatibility on Scala 2.11.
I'll be glad when we won't have to support Scala 2.11 anymore.
* the cancel/restore example above, but require access to the | ||
* result of `F[A]` | ||
*/ | ||
def onCancel[A](fa: F[A])(finalizer: F[Unit]): 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.
Unfortunately this too breaks binary compatibility on Scala 2.11.
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.
This is a pretty cool development, I wanted something like continual
for a long time.
However if we end up adding it as more than a function, I don't think it should be on Concurrent
. I think it should be on Bracket
and the reason for that is because we want continual
in cases in which it isn't reasonable to request Concurrent
as a restriction.
Consider that for data types that that don't implement Concurrent
, the continual
op is attempt.flatMap
.
I would actually be keen on adding it as a function in 2.0, however inefficient it might be, and then have a broader discussion around our cancelation model. mask {
poll { sem.acquire }.flatMap(yourThing)
} After that, I think ZIO implemented something very similar with |
This looks good and useful pre Cats Effect 3.0. I do think it's worth explicitly documenting that When used right-associativity, fa.flatMapCont(a => fb.flatMap(b => fc.flatMap(c => fd.flatMap(...)))) You can see that the first continual (which I have called This is of course less constraining than But the behavior of "continual" uncancelability after that first "flatMap" may be surprising and could benefit from documentation. I would not be a fan of this operation ending up on "Cats Effect 3.0", but whatever operations end up on the table, I agree with Alex it should end up on a trait so library authors can specialize. e.g.: def continual[A, B](fa: Task[A])(f: Either[Throwable, A] => Task[B]) =
ZIO.uninterruptible {
ZIO.interruptible(task).either.flatMap(f)
} Finally, as I have pointed out many times now, both here and on ZIO, any attempt to make acquisition interruptible in an Let's say the interruptible acquisition operation intAcquire = foo *> bar *> baz Now let's say this is somehow safe, such that continual(intAcquire)(k => ...) However, a subtle, benign change to intAcquire = foo *> bar *> baz <* F.pure(()) One can see that the resource safety of the code being so fragile to seemingly benign refactorings is a sign that something is deeply wrong with the model; indeed, there is a scenario with the above code that actually breaks the right identity monad law. I briefly discussed with @alexandru and suggested an An alternative is to say |
}.attempt | ||
)(_ => r.get.void) | ||
.flatMap(_ => r.get.rethrow) | ||
} |
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.
This is all very clever and terrifying. I look forward to when we have a more primitive implementation.
I think John's point is a good one about the subtle perils of this sort of stuff, but there's a broader underlying point beneath it, which is that intuitions about bind associativity simply do not apply in these "bind like" operators. This is one reason why I think Perhaps a less abstract way of putting this would be that users should not rely on the coincidental uninterruptability of effects within a bind chain passed to // given
continual(fa *> fb)(b => g(b)) In this chain, only the bind between |
@SystemFw Can you merge and correct build failures? |
Codecov Report
@@ Coverage Diff @@
## master #519 +/- ##
==========================================
+ Coverage 89.71% 89.81% +0.09%
==========================================
Files 71 71
Lines 2051 2071 +20
Branches 140 125 -15
==========================================
+ Hits 1840 1860 +20
Misses 211 211 |
This PR adds
onCancel
to execute an action on cancelation, andcontinual
, an interruption safe equivalent ofattempt.flatMap
.Although there is a longer discussion to be had wrt our cancelation model, in this PR I focused exclusively on implementing things in combinator land, with no internal changes: these should be usable today, by any effect. The main drawback right now is the heaviness of
continual
.MiMa tells me this is fully binary compatible.( -.-" )Breaks bincompat on 2.11 only