-
Notifications
You must be signed in to change notification settings - Fork 521
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
IOLocal
: MTL Local
instance & semantics
#3385
Comments
IOLocal
: MTL Local
instance semanticsIOLocal
: MTL Local
instance & semantics
Would you construct it from an extant |
IMO this should be the "blessed" way, but I think the alternative constructor is good to have around too. |
An attractive feature of MTL local is that it surfaces the inherent limitations at compile time. For example, scoping the use of a resource. By signature, you can't! def localK[F[_], E](f: E => E)(implicit L: Local[F, E]) =
new (F ~> F) {
def apply[A](fa: F[A]): F[A] =
L.local(fa)(f)
}
implicit def effectLocal[E](implicit ioLocal: IOLocal[E]) = new Local[IO, E] {
val applicative = Applicative[IO]
def ask[E2 >: E] = ioLocal.get
def local[A](fa: IO[A])(f: E => E): IO[A] =
ioLocal.modify(e => (f(e), e)).bracket(_ => fa)(ioLocal.set)
}
implicit def streamLocal[E](implicit ioLocal: IOLocal[E]) = new Local[Stream[IO, *], E] {
val applicative = Applicative[Stream[IO, *]]
def ask[E2 >: E]: Stream[IO, E2] = Stream.eval(ioLocal.get)
def local[A](stream: Stream[IO, A])(f: E => E): Stream[IO, A] =
stream.translate(localK(f)(effectLocal(ioLocal)))
}
implicit def resourceLocal[E](implicit ioLocal: IOLocal[E]) = new Local[Resource[IO, *], E] {
val applicative = Applicative[Resource[IO, *]]
def ask[E2 >: E]: Resource[IO, E2] = Resource.eval(ioLocal.get)
def local[A](r: Resource[IO, A])(f: E => E): Resource[IO, A] =
r.mapK(localK(f)(effectLocal(ioLocal)))
}
IOLocal(0).flatMap { implicit ioLocal =>
val r = Local[Resource[IO, *], Int].scope(Resource.unit)(1)
// use/surround has to happen outside scope, will be 0
.surround(Local[IO, Int].ask[Int])
val s = Local[Stream[IO, *], Int].scope(
// ask is inside scope, will be a stream of 1
Local[Stream[IO, *], Int].ask[Int])(1)
.compile.toVector
(r, s).tupled
} |
I'm increasingly convinced this is a good idea. Would a concrete PR to discuss be helpful? |
I'm obviously in favor of moving forward with this, and I think a PR would be a great next step :) A question at the back of my mind has been where to expose this, so that it doesn't get muddied with the lower-level object IO {
def local[A](initial: A): IO[Local[IO, A]] = ???
} |
What is the current state of this issue? Is there any chance we can have it in the upcoming 3.6.0 release? |
I'm still hemming and hawing about the direct Cats MTL dependency from Core. But the reality is this is probably a very good idea. |
Can we apply some democracy 🦅 here and overthrow Daniel's hemming and hawing? 😅 |
@kubukoz last that Daniel and I talked I think he's come around on adding this. |
Confirming that we should just add this. I increasingly think that Cats MTL is a pretty important pillar of improving the ergonomics of our ecosystem, so it kind of makes sense that it ends up everywhere. |
@bpholt recently brought up the issue of providing an MTL
Local
instance forIOLocal
in armanbilge/oxidized#9 (comment). That repo exists because Cats MTL can't provide it (since it creates a dep cycle with CE), and Cats Effect hasn't provided it (so far). Here are some reasons we should reconsider.Furthermore: there is a lot of frustration about the semantics to expect from
IOLocal
e.g. #3100, typelevel/fs2#2842.The interesting thing is, if you use
IOLocal
only as aLocal
(i.e. none of its lower-level methods) I am pretty sure you won't run into weird behavior like that.Local
encodes semantics that make it safe to useIOLocal
, without leaking implementation details about howStream
or whatever usesFiber
s under-the-hood. Of course, it is less powerful when used this way, but that's par-for-the-course.So I think there's an argument to push
Local
as the blessed way to interact with anIOLocal
, unless you are specifically doing low-level stuff. Think.background
to.start
. And that could be another good reason to provide such an instance directly.Footnotes
although I suppose in the current state we could break the testkit if MTL had to break, and it would be less bad than breaking core. ↩
The text was updated successfully, but these errors were encountered: