cats-effect icon indicating copy to clipboard operation
cats-effect copied to clipboard

Replace non-`Gen` kernel variants with existential form

Open djspiewak opened this issue 2 years ago • 9 comments
trafficstars

Right now, Concurrent[F[_]] = GenConcurrent[F, Throwable]. This matches our general opinion of the most common use-case, but there's a second extremely common scenario that we currently don't offer the same convenience around: Concurrent[F, _]. This is to say, the scenario in which you want concurrency but you don't care about the error handling. Arguably, this is even more common than the Throwable case.

Thinking through possible alias names here, it occurred to me that even Concurrent is actually quite inconsistent with the MonadThrow and MonadCancelThrow convention. In my opinion, an ideal configuration would be the following:

type Concurrent[F[_]] = GenConcurrent[F, _]
type ConcurrentThrow[F[_]] = GenConcurrent[F, Throwable]

This would also subtly encourage people to default to parametrically ignoring errors whenever possible, and would in turn make the types in downstream declaration sites a lot more helpful.

The fun thing is that we could actually do this in a fully binary compatible fashion! It would, however, break a lot of source code if we just did it blindly, so we would probably want to scalafix the change and encourage people to manually inspect the results.

Opinions wanted.

djspiewak avatar Apr 28 '23 20:04 djspiewak

Wayyyyyyy too much source breakage for a 3.x release.

mpilquist avatar Apr 28 '23 21:04 mpilquist

Wayyyyyyy too much source breakage for a 3.x release.

I mean, is it? The argument is basically that, in most cases, you would actually want the new Concurrent anyway and not ConcurrentThrow, and we could scalafix to the latter pessimistically just to avoid breaking anything. So this doesn't necessarily increase migration burden because the whole thing can be automatically transformed. It only becomes a burden when you want to work your way back to the looser constraint.

djspiewak avatar Apr 28 '23 21:04 djspiewak

I have a strong suspicion that scalafix is rarely used when dealing with library upgrades. Instead, someone will want to use a random library downstream, get the cats-effect 3.x upgrade transitively, and manually work through the errors after consulting release notes, issue trackers, chat, etc.

mpilquist avatar Apr 28 '23 21:04 mpilquist

I have a strong suspicion that scalafix is rarely used when dealing with library upgrades

At least in my experience, most people are either 1) not upgrading anything, 2) tasking some poor soul with big bang upgrading everything once a year or so, or 3) just spinning up Scala Steward (publicly or privately). In all three of these cases, this problem would get caught quickly and directly (either automatically or manually). I think the folks who fall outside this set are a pretty small minority, but I'll admit this is pretty biased by the teams that I've interacted with.

djspiewak avatar Apr 28 '23 21:04 djspiewak

We have Scala Steward. It doesn't run Scalafix on transitive updates. Whenever Cats Effect releases, a bunch of libraries also release. If we're lucky, we'll get one green needle in the haystack of red. If we didn't depend on CE explicitly, things are just broken and nobody has time to dig out. I can barely get teams to merge their green ones. I want to run into the wilderness and write Perl.

rossabaker avatar Apr 29 '23 01:04 rossabaker

What are some examples of things that are Concurrent but not ConcurrentThrow?

Jasper-M avatar Apr 29 '23 10:04 Jasper-M

What are some examples of things that are Concurrent but not ConcurrentThrow?

Anything which doesn't use raiseError, handleError, or anything error-related. Picking a random example from Fs2, I'm pretty sure broadcastThrough is Concurrent and not ConcurrentThrow: https://github.com/typelevel/fs2/blob/main/core/shared/src/main/scala/fs2/Stream.scala#L234

So really the argument here is the fact that almost everyone is over-constraining their function signatures, and by nudging the ecosystem in the right direction we can create a more granular error-handling mechanism.

djspiewak avatar Apr 29 '23 14:04 djspiewak

Sorry, I meant examples of typeclass instances.

Jasper-M avatar Apr 29 '23 15:04 Jasper-M

Sorry, I meant examples of typeclass instances.

Oh this wouldn't be an instance definition-side thing. Pretty much all practical instances will be ConcurrentThrow, or at most GenConcurrent. This proposal is more about suggesting that function definitions should be parametric in their error when they don't care. It's encoding a bit more information into the type.

djspiewak avatar Apr 29 '23 16:04 djspiewak