-
Notifications
You must be signed in to change notification settings - Fork 522
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
- generalize scope
function
#3360
IOLocal
- generalize scope
function
#3360
Conversation
It looks like the test is flaky:
|
@@ -201,15 +201,15 @@ sealed trait IOLocal[A] { | |||
* for { | |||
* local <- IOLocal(42) | |||
* _ <- local.get // returns 42 | |||
* _ <- local.scope(0).surround(local.getAndSet(1)) // returns 0 | |||
* _ <- local.scope(_ => 0).surround(local.getAndSet(1)) // returns 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have an example which actually uses the parameter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. I updated the example
841238a
to
6ad666c
Compare
9a88fc1
to
46da76d
Compare
This isn't an MTL Local, but the old scope signature is closer to what is traditionally called scope. I like the new signature, but I'm not so sure about the name. |
Hmm good point. It is sort of like a Speaking about the signature: is it even safe to expose this as a |
Have to work a little harder with pure cats-effect, but the old fs2 footgun is still present: IOLocal(0).flatMap { local =>
(Stream.resource(local.scope(1)) >> Stream.eval(local.get))
.map("Context is "+_)
.compile
.lastOrError
}
|
Ack. Maybe we should go back on this 😕 Breadcrumb to #3385. |
To be fair, it does what it says on the tin: /*
* Creates a scope with the given value. The original value is restored upon the finalization
* of a resource. It means all changes made inside of the resource will not be propagated to
* the outside. The resource didn't leak to the outside. We just are surprised to find ourselves on the outside, and had to lift into some foreign effect to do it. It's not the answer for tracing1, but arguably "not CE's problem." I'd be more upset about this with an example that's:
Footnotes
|
@rossabaker stupid enough for ya? 😛 //> using lib "org.typelevel::cats-effect::3.4.6"
import cats.effect._
import cats.syntax.all._
object App extends IOApp.Simple:
extension [A](local: IOLocal[A])
def scope(value: A): Resource[IO, Unit] =
Resource.make(local.getAndSet(value))(p => local.set(p)).void
def run = IOLocal(0).flatMap { local =>
local.scope(1).race(local.scope(2)).surround(local.get.flatMap(IO.println))
} |
Hmm. Yeah, that's a better footgun than my FS2 example, because it's just IO. But, still, all the method promises is that changes are not visible on the outside, not that they're always visible on the inside! |
This "fiber ref"'s scope makes Stream behave to my intuition. I'm still contemplating what Arman's example should return. I'm still at the flinging-poo-at-the-wall phase rather than the principled phase. //> using scala "2.13.10"
//> using lib "org.typelevel::cats-effect:3.4.6"
//> using lib "org.typelevel::cats-mtl:1.3.0"
//> using lib "co.fs2::fs2-core:3.5.0"
//> using plugin "org.typelevel:::kind-projector:0.13.2"
import cats._
import cats.effect._
import cats.effect.syntax.all._
import cats.effect.std._
import cats.mtl._
import cats.syntax.all._
import fs2._
trait FiberRef[F[_], A] {
def get: F[A]
def scope(a: A): Resource[F, Unit]
}
object FiberRef {
def ioFiberRef[A](a: A): IO[FiberRef[IO, A]] =
for {
ref <- Ref.of[IO, A](a)
local <- IOLocal(ref)
} yield new FiberRef[IO, A] {
def get: IO[A] =
local.get.flatMap(_.get)
def scope(a: A): Resource[IO, Unit] =
for {
oldRef <- Resource.eval(local.get)
newRef <- Resource.eval(oldRef.get.map(Ref.unsafe[IO, A]))
_ <- Resource.make(local.set(newRef))(_ => local.set(oldRef))
ref <- Resource.eval(local.get)
_ <- Resource.make(ref.getAndSet(a))(ref.set)
} yield ()
}
}
object Main extends IOApp.Simple {
implicit class IOOps[A](ioLocal: IOLocal[A]) {
def scope(value: A): Resource[IO, Unit] =
Resource.make(ioLocal.getAndSet(value))(p => ioLocal.set(p)).void
}
def run =
demo("basic stream", basicStream) >>
demo("interrupted sream", interruptedStream) >>
demo("resources", resources)
def demo[A](label: String, prog: IO[A]): IO[Unit] =
prog.flatMap(value => IO.println(s"$label: $value"))
// prints 1
def basicStream =
FiberRef.ioFiberRef(0).flatMap { fiberRef =>
(Stream.resource(fiberRef.scope(1)) >> Stream.eval(fiberRef.get))
.compile
.lastOrError
}
// prints 1
def interruptedStream =
FiberRef.ioFiberRef(0).flatMap { fiberRef =>
(Stream.resource(fiberRef.scope(1)) >> Stream.eval(fiberRef.get))
.map(identity)
.compile
.lastOrError
}
// prints 0
def resources =
FiberRef.ioFiberRef(0).flatMap { fiberRef =>
fiberRef.scope(1).race(fiberRef.scope(2)).surround(fiberRef.get)
}
} |
If I may fling some 💩 too: consider also the local.scope(1).both(local.scope(2)).surround(local.get.flatMap(IO.println)) I can't see how that's supposed to do something sensible at all :) |
That's a good example. For the I think I'm okay with the Now to reconcile this with my spittle at what happens in FS2 without extraordinary Ref efforts... |
Yeah, I wasn't so okay with it in the end 😕 I opened a PR to remove this method. |
Follow up to #3214 (comment)