-
Notifications
You must be signed in to change notification settings - Fork 36
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
Does tracing work well without IOLocal? Should it? #88
Comments
The tracing scope is managed by TraceScope. Specifically, only one method needs to be implemented: AFAIK we cannot manipulate Kleisli's value within Resource, aren't we? def createScope(scope: String): Resource[Kleisli[F, String, *], Unit] =
Resource.make(Kleisli.ask <* Kleisli.pure(scope))(p => Kleisli.pure(scope))
scope("32").use(current => Kleisli.liftF(IO.println(current))).run("42") // prints "42" Since Natchez does not offer a Since |
I like the concept of For some users, this behavior may be a deal breaker. Therefore, supporting |
It would be interesting to see a trait Resource[F[_], A] {
def intercept(f: F ~> F): Resource[F, A] = ???
}
val res = Resource
.unit[Kleisli[IO, String, *]]
.intercept(new (Kleisli[IO, String, *] ~> Kleisli[IO, String, *]) {
def apply[A](f: Kleisli[IO, String, A]): Kleisli[IO, String, A] =
Kleisli.liftF(f.run("intercept"))
})
res.use(current => IO.println(current)).use("external") // prints "intercept" It sounds fun, but it may be dangerous and break a few laws. |
Sorry, can you clarify this point? What is a situation where Kleisli works, but |
@armanbilge unless I'm missing something, I do not understand how to implement the following service in terms of Kleisli: case class State(value: String)
trait LocalState[F[_]] {
def current: F[State]
def scope(next: State): Resource[F, Unit]
} The snippet//> using scala "2.13.10"
//> using lib "org.typelevel::cats-effect::3.4.5"
import cats.data.Kleisli
import cats.effect.{IO, IOApp, IOLocal, Resource, Sync}
import cats.effect.std.Console
import cats.syntax.functor._
import cats.syntax.flatMap._
object Main extends IOApp.Simple {
case class State(value: String)
trait LocalState[F[_]] {
def current: F[State]
def scope(next: State): Resource[F, Unit]
}
def ioLocal(initial: State): IO[LocalState[IO]] =
IOLocal(initial).map { local =>
new LocalState[IO] {
def current: IO[State] =
local.get
def scope(next: State): Resource[IO, Unit] =
Resource.make(local.getAndSet(next))(previous => local.set(previous)).void
}
}
type K[A] = Kleisli[IO, State, A]
def kleisli: LocalState[K] =
new LocalState[K] {
def current: Kleisli[IO, State, State] =
Kleisli.ask
def scope(next: State): Resource[K, Unit] =
??? // how to implement the scope?
}
def test[F[_]: Sync: Console](state: LocalState[F]): F[Unit] =
for {
current <- state.current
_ <- Console[F].println(current)
_ <- state.scope(State("scope-specific")).surround {
state.current.flatMap(state => Console[F].println(state))
}
} yield ()
def run: IO[Unit] = {
val initial = State("initial")
ioLocal(initial).flatMap(state => test(state))
// output:
// State(initial)
// State(scope-specific)
//test(kleisli).run(initial)
}
} Nvm, I misread your question. In some scenarios, IOLocal behaves differently than Kleisli: |
I'm familiar with those issues, but they rely on APIs that are not available for |
What on earth, I missed a whole conversation. Anyway, #102 and #103 offer competing views of the world, via cats-mtl. As discussed in the Natchez Resource Debates of 2022, there is a fundamental difference between Stateful and Local (what I called "stateless" in that repo).
I think it's clear that -- I wanted |
What is the signature of |
From @iRevive above:
|
WIP: I have a gist that shows:
Challenge: write a program where |
I haven't made anything bad like typelevel/cats-effect#3100 happen yet, but typelevel/fs2#2842 is a real problem for def fs2_2842(T: Trace[IO]) = {
val s =
Stream.eval(T.current.flatMap(IO.println)) >>
Stream.resource(T.spanR(1)) >>
Stream.eval(T.current.flatMap(IO.println)) >>
Stream.resource(T.spanR(2)) >>
Stream.eval(T.current.flatMap(IO.println))
IO.println("interrupt") >> s.interruptScope.compile.drain >>
IO.println("scope") >> s.scope.compile.drain >>
IO.println("plain") >> s.compile.drain
}
|
Limiting def localIO[E](e: E): IO[Local[IO, E]] =
IOLocal(e).map { ioLocal =>
new Local[IO, E] {
def ask[E2 >: E] = ioLocal.get
val applicative = Applicative[IO]
def local[A](fa: IO[A])(f: E => E) =
ioLocal.modify(p => (f(p), p))
.bracket(Function.const(fa))(ioLocal.set)
}
} That impacts trait Trace[F[_]] {
def span[A](id: Int): Trace.SpanOps[F, A]
def current: F[Int]
}
object Trace {
class SpanOps[F[_], A](id: Int)(implicit L: Local[F, Int]) {
// use this one to add events, links, etc.
def use(k: Span => F[A]): F[A] =
L.scope(k(Span(id)))(id)
// use this just to span an effect
def surround(k: F[A]): F[A] =
L.scope(k)(id)
// more resource-like methods?
// maybe these go on SpanBuilder?
}
// ways to lift to Stream, Resource
} Use looks like what we have. The big bummer is that Trace.ioLocal.flatMap { implicit T =>
T.span(1).use { span =>
IO.println("Current span is "+span) >>
T.current.flatMap(id => IO.println("Span context ID is "+id)) >>
T.span(2).surround {
T.current.flatMap(id => IO.println("Span context ID is "+id))
}
}
} |
I was thinking about something similar to What are the benefits of expressing span as a
Can we do the following with val res: Resource[IO, Connection] =
for {
_ <- T.span("first-step").start
c <- makeConnection
_ <- T.span("second-step").start
} yield c
val r: IO[Unit] = res.use(_ => T.current.flatTap(IO.println)) // should be 'second-step' span I guess the closest point we can get is: val r: IO[Unit] =
Resource(T.span("first-step").surround(makeConnection.allocated)).use { c =>
T.span("second-step").surround {
T.current.flatTap(IO.println)
}
} By the way, do |
Yeah, in the resource example, I expect |
We now support any |
Natchez has
Trace
implementations forIOLocal
,cats.mtl.Local
, and various transformer stacks.IOLocal
is different in that it's "stateful". It's probably the most ergonomic, but it also has some limitations discussed on #37.Should we be supporting the
cats.mtl.Local
instances as well? If we do, what, if anything, needs to change?The text was updated successfully, but these errors were encountered: