Skip to content

Add compute combinators - #4190

Open
BalmungSan wants to merge 10 commits into
typelevel:series/3.xfrom
BalmungSan:add-compute-combinators
Open

Add compute combinators#4190
BalmungSan wants to merge 10 commits into
typelevel:series/3.xfrom
BalmungSan:add-compute-combinators

Conversation

@BalmungSan

Copy link
Copy Markdown
Contributor

Related to #3353

Adds a couple of useful combinators related to compute-bound operations.
How best to suspend compute-bound operations is a FAQ in Discord, thus I believe having some combinators for the simple use cases is a good idea.

@BalmungSan
BalmungSan force-pushed the add-compute-combinators branch from 3714fd7 to d0950f7 Compare December 3, 2024 20:43
@BalmungSan
BalmungSan force-pushed the add-compute-combinators branch from d0950f7 to e84d14b Compare December 3, 2024 21:27
@satorg

satorg commented Dec 4, 2024

Copy link
Copy Markdown
Contributor

I would like to weigh in a little.
I'm not sure that adding these two methods: computeAttempt and computeMapAttempt is really worth it:

def expensiveComputation: Either[Throwable, TheResult] = ???

computeAttempt { expensiveComputation } <=?=> compute { expensiveComputation }.rethrow

Thereby, computeAttempt doesn't look like a big win.

Is it really important to have rethrow executed before the finalizing cede rather than after it?

@satorg

satorg commented Dec 4, 2024

Copy link
Copy Markdown
Contributor

That said, compute and computeMap look like a big deal to me...

@BalmungSan

Copy link
Copy Markdown
Contributor Author

I'm not sure that adding these two methods: computeAttempt and computeMapAttempt is really worth it:

As with any combinator is hard to argue both ways IMHO.
They are there for convenience, but I do agree it is impractical to add each and every combinator, as Fabio points regularly.
Like, compute is also just convenience, but I do agree the name would help and it seems folks are reluctant of using cede.

Personally, I like avoiding the rethrow but I also understand that it may not be seen as much, so I am happy to remove them if others agree.

@djspiewak djspiewak added this to the v3.7.0 milestone Dec 20, 2024
@armanbilge armanbilge modified the milestones: v3.7.0, v3.8.0 Apr 13, 2025
@reardonj

reardonj commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

I'd also be inclined to remove the attempt versions. These combinators are already slightly niche, and having Either[Throwable, A] is an unusual situation to me. I'm not sure how you end up with that without already having a try and conversion to Either, but IO already will capture the exception and throw. So why even try?

Also, the -attempt suffix feels odd since a regular attempt turns an error into an Either while this one goes backwards, turning an Either into an error.

@BalmungSan

Copy link
Copy Markdown
Contributor Author

I'd also be inclined to remove the attempt versions. These combinators are already slightly niche, and having Either[Throwable, A] is an unusual situation to me.

Well, actually, the last time I needed this it was with that shape :p
It was a heavy Json parser which returned Either[Throwable, A]. But sure, adding a rethrow at the end wouldn't have been a big deal.

As I said in my previous message, it is hard to argue which combinators should exist and which should not. I just think we need at least the two basic compute and computeMap

@reardonj

reardonj commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

As I said in my previous message, it is hard to argue which combinators should exist and which should not. I just think we need at least the two basic compute and computeMap

Ya, those made sense to me. It's just the attempt shape and name I object to. I could see an argument that only IO.compute is needed and you can just flatMap if you need a map too, but getting an A from something and doing compute on it is such an obvious use case that computeMap makes sense to have.


I worry a little this whole exercise will result in people casually throwing a compute on anything that does CPU bound work at all, but that's not even a footgun.

* @see
* [[delay]] for more details
*/
def compute[A](thunk: => A): F[A] =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This having to be in Async feels bad. Is there a reason we shouldn't implement it as F.defer(F.pure(thunk)) instead of delay(thunk)? delay is supposed to be for side effects, but the computations should be pure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or even a cheeky F.unit.map(_ => thunk). It wouldn't be the first use of the trick in this codebase.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants