cats-effect
cats-effect copied to clipboard
Introduce an `unyielding { ... }` construct
Spinning out of https://github.com/typelevel/cats-effect/pull/2478#issuecomment-982030581. tl;dr, a way to prevent (auto?) cedes/yields for a region of the program.
I think it would probably also suppress regular cedes. So something like:
unyielding(cede) <-> unyielding(unit)
Also maybe unceding would be a better name. Or… something
uncedable :P
undecedable
It this the same idea as tokio::task::unconstrained?
https://docs.rs/tokio/latest/tokio/task/fn.unconstrained.html
Nice catch! Yes it is.
Another thought about this ... in JS land, can't we very nearly cheat with:
def undecedable[A](fa: F[A]) = evalOn(fa, JSExecutionContext.queue)
Is that the promises-based one? If so then yes, that would basically work. :-P
Yeah 😆 for this to work it assumes that you are currently on some macrotask-based executor.
But the thing is, all the instances that I've wanted undecedable is specifically to do stuff like registering callbacks before I/O events start triggering. I.e. it's less about preventing ceding altogether, and more about preventing ceding with respect to I/O.
So this would exactly achieve that semantic without requiring any changes from implementers. I think this should do just fine for JS.
Forgot to add some notes from another recent discussion on this. Specifically, what to do about:
IO.undecedable(IO.sleep(...))
or evalOn(...) or async(...).
I think an important distinction to make here is that when you are reaching for undecedable it's not because you are looking to suppress IO.cede specifically, it's because you are looking to suppress rescheduling for some region of code.
In code you 100% control, you can avoid those problem combinators the only rescheduling to worry about is auto-cedes from the runtime. But if you are applying it to arbitrary code (and the type system won't stop you!) then it could easy contain operations that unavoidably require rescheduling. It's a leaky abstraction.
The undecedable operation is really only safe in the context of an effect that is at most Concurrent+Sync, and not Temporal or Async. But we don't have a great way to express that right now.
See also the definition of Cont which relies on higher-rank polymorphism to apply this sort of constraint, but at least there it is safe to lift F ~> G, which would not be the case here.
After writing all of that 😂 maybe the best we can do (and we can do today, without breaking compat!) is introduce a new API on IO only, which is simply noAutoCede(...).
I'm wondering if a syncStep-like construct would make sense here: "run this without rescheduling until you can, then return the rest (if any) in an F[A]"?
Interesting idea, but I'm not sure. syncStep is helpful because if it works, you can "short-circuit" with the synchronous result and take a different path of execution with that result, that is not possible otherwise.
In this case, I don't think there is really a branch (unless I missed your point :). Either way in the end, you end up with F[A], and it either executed with the semantics you wanted, or it didn't.
I don't know... yes, if you want no rescheduling, you probably absolutely need it. So you're probably right.
The undecedable operation is really only safe in the context of an effect that is at most Concurrent+Sync, and not Temporal or Async. But we don't have a great way to express that right now.
I understand Sync, but if it's Concurrent, how do you do join?
Hmm you are right! join also wouldn't work, because it requires asynchronous suspension 🤔 so that rules out Concurrent and Spawn. But then we don't even have a concept of cede ... and maybe that's the point. So Sync is the best you can do.