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

Introduce an unyielding { ... } construct #2610

Open
armanbilge opened this issue Nov 29, 2021 · 16 comments
Open

Introduce an unyielding { ... } construct #2610

armanbilge opened this issue Nov 29, 2021 · 16 comments

Comments

@armanbilge
Copy link
Member

armanbilge commented Nov 29, 2021

Spinning out of #2478 (comment). tl;dr, a way to prevent (auto?) cedes/yields for a region of the program.

@djspiewak
Copy link
Member

I think it would probably also suppress regular cedes. So something like:

unyielding(cede) <-> unyielding(unit)

@djspiewak
Copy link
Member

Also maybe unceding would be a better name. Or… something

@armanbilge
Copy link
Member Author

uncedable :P

@djspiewak
Copy link
Member

undecedable

@armanbilge
Copy link
Member Author

It this the same idea as tokio::task::unconstrained?
https://docs.rs/tokio/latest/tokio/task/fn.unconstrained.html

@djspiewak
Copy link
Member

Nice catch! Yes it is.

@armanbilge
Copy link
Member Author

Another thought about this ... in JS land, can't we very nearly cheat with:

def undecedable[A](fa: F[A]) = evalOn(fa, JSExecutionContext.queue)

@djspiewak
Copy link
Member

Is that the promises-based one? If so then yes, that would basically work. :-P

@armanbilge
Copy link
Member Author

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.

@armanbilge
Copy link
Member Author

armanbilge commented Feb 21, 2023

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.

@armanbilge
Copy link
Member Author

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(...).

@durban
Copy link
Contributor

durban commented Feb 21, 2023

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]"?

@armanbilge
Copy link
Member Author

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.

@durban
Copy link
Contributor

durban commented Feb 21, 2023

I don't know... yes, if you want no rescheduling, you probably absolutely need it. So you're probably right.

@durban
Copy link
Contributor

durban commented Feb 21, 2023

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?

@armanbilge
Copy link
Member Author

armanbilge commented Feb 21, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants