cats-effect
cats-effect copied to clipboard
Replace non-`Gen` kernel variants with existential form
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.
Wayyyyyyy too much source breakage for a 3.x release.
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.
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.
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.
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.
What are some examples of things that are Concurrent but not ConcurrentThrow?
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.
Sorry, I meant examples of typeclass instances.
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.