-
Notifications
You must be signed in to change notification settings - Fork 535
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
Proposal: Continual (type class?) #242
Comments
Must say that a problem is that I'm the only one currently campaigning for this or with an understanding that auto-cancelable binds fundamentally changes the behavior of polymorphic code and rendering certain techniques impossible. One one hand, if I have such a hard time explaining this to the other contributors, it might get really confusing for users and that's a big problem. On the hand I feel like I'm losing my mind. I can't be the only one noticing the huge difference and breakage. |
In the first example, This would coexist with #237, so auto-cancel run loops could be lawful |
This type class seems to exist so that Yet Rather than modifying cats-effect type classes because of bugs in downstream libraries, doesn't it make more sense to fix those bugs? |
Yes. Made a mistake.
Yes. What this would do is to turn auto-cancelable run loops off (or in case of our And it would also re-add the laws that we are changing. So the current laws that are failing in Also important to mention:
|
@jdegoes these are only bugs if you follow the world view that only your solution is the correct one. It cannot be a bug if the behavior was well defined from the beginning, even before Scalaz 8's IO existed. Also, I would prefer if we wouldn't start a debate on 3 or 4 separate threads. |
Is
In my opinion, consistency mandates either (1) or (2). I believe (1) is correct, but I think (2) is consistent as well (if not desirable, IMO). |
Neither of those claims is true, because in fact there's an option 3 ... Going to point yet again to my sample at #192 (comment) — and in particular the |
I've also added that sample in my summary here: https://gist.github.com/alexandru/b970186208d1c1378c903d43588bd6a7 |
Option 3 is not a good option, in my opinion. Option 3 suggests a library user who does not wish to use This alternate pathway to achieving "resource safety" exists solely because of a bug in Note that it is impossible to implement For example, Note also that interruptible binds are by no means the only way to provide interruption. For example, Scalaz 8 IO will interrupt async operations that have not completed, even if they have no canceler, by discarding their results upon completion; which allows, among other things, a law stating that The fact that Option 3 does not actually provide any guarantee of resource safety (in fact, it's guaranteed that code using |
John, now you're doing user profiling, invoking laziness. That's not a good argument. There is an Option 3 and Cats-Effect's
Not really the same thing, plus this is why we are talking of Option 3 — because you can still work with
I've been looking at handling You're attributing too many qualities to |
There is no way to write resource safe code without Well, I've said my 2 cents, I don't really have anything more to say on this issue.
|
Right, I think we've already said everything in #192. |
I have an open PR that proposes breaking changes the laws, but I don't find it fair or constructive to call constructs that pass the currently published laws as a "bug." We should be able to discuss the ramifications of these alternative designs without denigrating the work that was lawful when written. |
Take your pick: Either it’s a bug to assume resource safety without MonadBracket, or MonadBracket is not useful. See my original comment on the issue. If the former, the (broken) laws reflect a latent contradiction between the intent of the MonadBracket type class and assumptions made elsewhere. i.e. both laws and downstream software are defective with respect to the intent of MonadBracket. These things happen. Especially with complex hierarchies and when pushing new ground in abstracting over effect types. There’s no need to make a big deal of them or take them personally. Identification of bugs (if indeed that’s what this behavior is) is not denigration. Alex suggested Scalaz has a bug with respect to InterruptedException, which is not denigration but extremely useful information that can be used to improve the quality of software. |
We have to agree to disagree here. Note that my proposal is not changing Scalaz, but to find a way to keep supporting the current evaluation model while fixing the laws to allow for Scalaz's model and Note that the role of the type classes is to support other implementations. We don't want just But to be more concrete, Monix, Cats and Scalaz do not have the only implementations of IO on top of the JVM. There are other implementations that may end up supporting our type classes. For example the entire ReactiveX ecosystem (e.g. RxJava, RxJS, etc), which have been doing their own thing. RxJava for example has Single which is an IO monad, well not sure if it passes our laws, but if it has a stack safe And I can tell you that Also replied to @pchlupacek on another thread with another explanation of the difference, made another gist: https://gist.github.com/alexandru/a23790f4da9ec46f9eee722416e20cd7 Note how we still rely on |
Re: the latest gist. You are stumbling into the exact problem fs2 solves with Scopes, and more generally in Haskell/literature with RegionT/ResourceT/Managed. |
Yes, and @oleg-py and myself are investigating an alternative encoding.
Actually the right answer is to avoid synchronization altogether. STM is great when you're forced to do synchronization, but STM is terrible too: it's unpredictable, it has awful performance and you cannot escape Amdahl's law. Sometimes the best way to solve a problem is to eliminate it, instead of ending up into a hammer / nails loophole. I wouldn't have a problem with switching to this model entirely if this model simplified things. But it doesn't. I can see plenty of examples that are now more complicated. As examples:
When seeing such added complexity, I do have to wonder, how can this be the right answer? And because this behavior is not something that can be witnessed by usage of Now if we all agree that this model is the right own, screw other opinions, I'm fine with that — we'll go through a painful transition in Monix and be done with it. |
Note that we needed I also don't see how this problem can be ignored: right now I can write polymorphic code in Iterant that breaks if a Monix option is enabled. Even if you decide to go with As for That being said, I don't want to force the issue, these are just my opinions, so I'm keen to hear what the others think 😃 |
You're right about that one, I've been simply deferring the issue. The proposal in this issue would make it safe though, without excluding Scalaz or Monix's auto-cancelable option, being an add-on and it's better than what I had 3 months ago. We don't have to rush into pushing it that badly. Thinking today with a clearer head, we could even release it post 1.0, plus I really want this decision to be taken for the right reasons and not because my own code breaks. Therefore we could proceed with a release without |
Please do not change anything just for Scalaz IO. I think changes to this project should be done for the right reasons—i.e. they promote the development of correct software. I also think that supporting unprincipled and lawless effect types, or those that promote the development of incorrect software, is not a net positive and the project should not aim to sacrifice principles to gain inclusivity.
This already works and composes without issue. val task1 = F.bracket(acquire1)(use1)(release1)
val task2 = F.bracket(acquire2)(use2)(release2)
val task3 = task1 *> task2 In this example, In order to contrive an example that does not "work", you need For example, the following code: val task1 = F.bracket(acquire1)(resource1 => use1(resource1) *> F.point(resource1))(release1)
val task2 = task1.flatMap(resource1 =>
F.bracket(acquire2)(resource2 => use1(resource1) *> use2)(release2) In this example, This problem can be solved with type tricks, although whether or not the added encoding cost is worth the increase in safety is worth discussing. Now, if you need to use F.bracket(acquire1)(release1) { resource1 =>
F.bracket(acquire2)(release2) { resource2 =>
// Now can safely use `resource1` and `resource2`
}
} If both of these require access to an outer resource, that too can be described safely through nested brackets: F.bracket(acquire0)(release0) { resource0 =>
F.bracket(acquire1(resource0))(release1) { resource1 =>
F.bracket(acquire2(resource0))(release2) { resource2 =>
// Now can safely use `resource1` and `resource2`
}
}
} All these (non-leaky) compositions have guarantees that they can be interrupted at any point, or fail due to defect or catastrophic error, and all acquired resources will be released. You can even do opt-in, early resource release, if the inner task decides it is done with Assuming you are not leaking resources outside bracket—which results not in leaked resources, but in runtime errors due to using invalid resources—then bracket offers enough compositional power to use any number of resources in any combination. That doesn't mean it's always easy to use In order to understand the root of the problem, you must understand
Bracket lets you acquire and release resources around any choice of parentheses. We have to perform state management and have a separate compilation process if we want to acquire and release resources around any other combinations (for example, around just
The meaning of
It is simple in this way: if the code uses bracket for all resource acquisition and release, then it is resource safe, to the maximum extent possible (obviously a bug in It is not simple in that using bracket is nontrivial with higher-level resource management models. This example demonstrates that resource safety cannot be achieved with
If the code was relying on It is not only monad transformers for which
👏 You have my highest respect for being so objective about this issue, and I want to make it clear that although I think the current laws are broken and lead to resource unsafe code (such as |
Closing this, as at the moment I'm no longer interested in the proposal. |
Given the ongoing saga with #192, here's a summary of the problem:
LazyList
sample: https://gist.github.com/alexandru/b970186208d1c1378c903d43588bd6a7bracket
with mutexes: Proposal: Continual (type class?) #242So I propose the following addition ...
Also as an alternative, we could describe a separate type class that would work as a marker:
And then in signatures this would be relevant for folds:
UPDATE at 2018-05-25 10:49: So given this:
Within the
continual
model you get a guarantee that iftask1
has completed successfully, thentask2
and thusrelease2
will get evaluated, no matter what.task2
can still get cancelled while being evaluated, however you cannot get an interruption that happens in betweentask1
ortask2
.The
Concurrent
laws wouldn't be unlike what we have now:Basically under the auto-cancelable binds model, this should fail, but
continual
would make it succeed. And we need it at least onSync
because for implementations like Monix'sCoeval
orcats.effect.IO
it can just be the identity function, as they already are "continual".For Monix's
Task
on the other hand this operation would involvetask.executeWithOptions(_.disableAutoCancelableRunLoops)
, which would work fine.Motivation
Copy-pasted:
UPDATE at 2018-05-25 10:49: Also commented below:
The role of the type classes is to support other implementations. We don't want just
cats.effect.IO
, or justscalaz.effect.IO
or justmonix.eval.Task
. We want all of those and more, which is why I personally was open to support Scalaz's new IO, hence all this pain.But to be more concrete, Monix, Cats and Scalaz do not have the only implementations of IO on top of the JVM. There are other implementations that may end up supporting our type classes. For example the entire ReactiveX ecosystem (e.g. RxJava, RxJS, etc), which have been doing their own thing. RxJava for example has Single which is an IO monad, well not sure if it passes our laws, but if it has a stack safe
flatMap
then we should support it.And I can tell you that
Single
's cancelation model is that of the current Cats-Effect, much like the rest of ReactiveX. Which is significant because ReactiveX is massively popular, more popular than Akka, Cats and Scalaz combined, and I know that Kotlin's Arrow for example is providing type class instances for them.The text was updated successfully, but these errors were encountered: