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

cats.mtl.Local[IO, E] from IOLocal[E] #3429

Merged
merged 8 commits into from
Nov 25, 2024
Merged

cats.mtl.Local[IO, E] from IOLocal[E] #3429

merged 8 commits into from
Nov 25, 2024

Conversation

rossabaker
Copy link
Member

cats.mtl.Local's semantics are a safe and useful subset of what can be done with IOLocal. This provides easy access to a Local[F, E] instance from an IOLocal[E] value.

  • core takes on a cats-mtl dependency. Note that testkit already couples these release cycles.
  • Adds IOLocal#asLocal to get a Local view of an IOLocal value
  • Adds IOLocal.local to create an IOLocal and return a local view of it, inside IO.

A similar view is already in use in otel4s.

Fixes #3385.

Copy link
Member Author

@rossabaker rossabaker left a comment

Choose a reason for hiding this comment

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

Needs docs and such, but I wanted to float the interface first.

@@ -319,4 +332,6 @@ object IOLocal {
underlying.get.flatMap(s => underlying.reset.as(getter(s)))
}

def local[E](e: E): IO[Local[IO, E]] =
Copy link
Member Author

Choose a reason for hiding this comment

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

otel4s has integrated LiftIO, but I'm not sure this is necessary: cats.mtl already lifts a Local[F, E] to all the common transformers over F. Everyone would pay the LiftIO tax just to cover any LiftIO instances not covered by Local.

Comment on lines +256 to +257
def local[B](iob: IO[B])(f: A => A): IO[B] =
self.modify(e => f(e) -> e).bracket(Function.const(iob))(self.set)
Copy link
Member Author

Choose a reason for hiding this comment

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

This could conceivably be made a method on IOLocal itself as well, with this becoming a simple delegate.

Copy link
Member Author

Choose a reason for hiding this comment

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

I like thin instances, but if we're discouraging non-Local use of IOLocal, maybe we shouldn't make it easier to use.

core/shared/src/main/scala/cats/effect/IOLocal.scala Outdated Show resolved Hide resolved
@armanbilge armanbilge added this to the v3.6.0 milestone Feb 17, 2023
@djspiewak
Copy link
Member

core takes on a cats-mtl dependency. Note that testkit already couples these release cycles.

This is the one thing that gives me pause. I'm concerned about it mostly because I suspect there are breaking changes we will want to make to Cats MTL in the near-ish term, which transitively breaks Cats Effect. However, as you pointed out, testkit already implies this version dependency, which is unfortunate but definitely undercuts my concern.

@djspiewak
Copy link
Member

Particularly with an eye towards otel4s, are we sure we want to privilege bare IOLocal[A] in this way? My concern is that this ossifies a very particular propagation semantic rather than encouraging users to be more selective about what semantics they want.

@rossabaker
Copy link
Member Author

I don't see that otel4s privileging bare IOLocal. It's saying context propagation should be done with cats.mtl.Local semantics, because advanced but common uses of resource and race do weird shit. I fixed the FS2 problems under stateful propagation with blunt force more than thoughtful reason, but it still wasn't satisfactory.

I do want to be cautious here, because if cats-mtl is not stable, this PR makes everything unstable. But we hope to integrate otel4s into the major networking libraries, and without cats-mtl, we're missing a typeclass and then really are privileging IOLocal.

My two questions:

  1. If you were designing IOLocal today, what interface would you want beyond the operations of cats.mtl.Local? Remember the IOLocal can hold a Ref for cases children need to merge state back into the parent.

  2. (Perhaps a better issue for cats-mtl) After a long period of stability, what changes do you imagine in cats-mtl? If cats-mtl-2 is coming, that changes a lot of plans for a lot of projects.

@djspiewak
Copy link
Member

After a long period of stability, what changes do you imagine in cats-mtl?

I imagine very little changing in the typeclasses. I'm mostly interested in smoothing the introduction/elimination of capabilities, particularly those with lexical scopes (i.e. all the interesting ones). That's probably something that can be done in a binary-compatible fashion, but I don't know for sure until I really go deep on it.

@rossabaker
Copy link
Member Author

otel4s could kick this can down the road by not depending on cats-mtl, restricting itself to IOLocal operations representing local semantics, and either removing support or massively duplicating code for Kleisli and all transformers. natchez-core is an approximation of what that road looks like, and it would be nice not to repeat that aspect of it.

When you say "ossifies a very particular propagation semantic," otel4s is pretty much going to have to do so with respect to tracing. We can have a tracer that uses local semantics, which sucks for spanning Resource#use and is good for everything else. Or we can span with a Resource, which gives a lovely API as long everybody uses the orthodox resource interpreter, but corrupts when they don't. Perhaps there's something behind door number three, but even then, we'll need http4s and skunk and friends to pick the same door.

@rossabaker
Copy link
Member Author

Note that there is also lawful Stateful instance, which does weird shit with concurrency between .set and .get. Since it's lawful, it's not better nor worse than public .set. It's milquetoast to neither provide that instance nor push for deprecation of IOLocal's stateful bits, but here I sit... for now. I would love to see a practical example that can best be done as a Stateful view of IOLocal.

@kubukoz
Copy link
Member

kubukoz commented Sep 11, 2024

Note that there is also lawful Stateful instance, which does weird shit with concurrency between .set and .get.

I'm not sure I get the problem - one fiber's view of an IOLocal can't be changed by someone else between set and get, can it? We don't propagate updates from child fibers to the parent nor from the parent to the children, afaik, so each fiber (a unit of concurrent execution) would be immune to weird shit from others... unless you meant something else.

@djspiewak
Copy link
Member

Can we get a non-draft version of this?

@armanbilge armanbilge closed this Nov 24, 2024
@armanbilge armanbilge reopened this Nov 24, 2024
@armanbilge armanbilge changed the title cats.mtl.Local[IO, E] from IOLocal[E] cats.mtl.Local[IO, E] from IOLocal[E] Nov 24, 2024
build.sbt Outdated Show resolved Hide resolved
core/shared/src/main/scala/cats/effect/IO.scala Outdated Show resolved Hide resolved
core/shared/src/main/scala/cats/effect/IOLocal.scala Outdated Show resolved Hide resolved
@armanbilge armanbilge marked this pull request as ready for review November 24, 2024 22:28
armanbilge
armanbilge previously approved these changes Nov 24, 2024
build.sbt Outdated Show resolved Hide resolved
@djspiewak djspiewak merged commit eb918fa into series/3.x Nov 25, 2024
30 of 34 checks passed
@armanbilge armanbilge deleted the mtl branch November 25, 2024 18:01
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.

IOLocal: MTL Local instance & semantics
5 participants