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

Add Tracer.translate #264

Closed
wants to merge 7 commits into from
Closed

Add Tracer.translate #264

wants to merge 7 commits into from

Conversation

iRevive
Copy link
Contributor

@iRevive iRevive commented Jul 3, 2023

Proof of concept for #261

@@ -172,6 +174,8 @@ trait Tracer[F[_]] extends TracerMacro[F] {
*/
def noopScope[A](fa: F[A]): F[A]

def mapK[G[_]: Sync](fk: F ~> G, gk: G ~> F): Tracer[G]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here the type constraint must satisfy SpanRunner and TraceScope from the java-trace package.

@@ -172,6 +174,8 @@ trait Tracer[F[_]] extends TracerMacro[F] {
*/
def noopScope[A](fa: F[A]): F[A]

def mapK[G[_]: Sync](fk: F ~> G, gk: G ~> F): Tracer[G]
Copy link
Member

Choose a reason for hiding this comment

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

if you need both F ~> G and G ~> F then actually it's an imapK 😜 also sometimes the method is called translate.

Also, Sync is a very strong constraint, and I don't think it's necessary. Once you have F ~> G and G ~> F then you should be able to derive Sync[G] from Sync[F].

See a similar idea in:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

translate is more suitable indeed.

Once you have F ~> G and G ~> F then you should be able to derive Sync[G] from Sync[F].

I didn't know that! I will take a look at the example, looks interesting.

Choose a reason for hiding this comment

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

If you need F ~> G and G ~> F, then the invariant nature means almost all interesting transformers are eliminated from use, OptionT, EitherT, Kleisli, IorT, etc.

Copy link

Choose a reason for hiding this comment

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

Random thought: you might be able to do this with something weaker than F ~> G + G ~> F, like Haskell's UnliftIO. That only gets you Kleisli while staying sensible, but it's something at least.

Prototype: Prillan@355c68d

@iRevive iRevive changed the title Add Tracer.mapK Add Tracer.translate Jul 3, 2023

object TracerImpl {

private def liftSync[F[_], G[_]](
Copy link
Member

Choose a reason for hiding this comment

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

Nice! We might consider upstreaming these methods to Cats Effect is they are generally useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what I was thinking about too

# Conflicts:
#	core/trace/src/main/scala/org/typelevel/otel4s/trace/Tracer.scala
@iRevive
Copy link
Contributor Author

iRevive commented Jul 24, 2023

I remembered the implicit class XXXSyntax[F[_]](...) trick, so the API is slightly cleaner and does not leak into the implementation.

A few notes:

  1. There is a generic API mapK and translate
  2. For some components, mapK cannot be implemented, since the fg: G ~> F is still required. So I added translate instead.
  3. liftOptionT requires a few copy-pastes, but compiles fine

@iRevive iRevive marked this pull request as ready for review July 24, 2023 14:56
@iRevive
Copy link
Contributor Author

iRevive commented Jul 24, 2023

@NthPortal - would you like to take a look? These changes intersect with your #261 PR.

Comment on lines 159 to 166
implicit final class SpanBuilderSyntax[F[_]](
private val builder: SpanBuilder[F]
) extends AnyVal {

def translate[G[_]](fk: F ~> G, gk: G ~> F)(implicit
F: MonadCancel[F, _],
G: MonadCancel[G, _]
): SpanBuilder[G] =
Copy link
Contributor

Choose a reason for hiding this comment

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

is the reason for this so that the primary API doesn't contain MonadCancel?

(side note: I thought it needed MonadCancelThrow specifically)

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, do we really need both MonadCancel for F and G? If we have both fk and gk seems like we should be able to derive it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is the reason for this so that the primary API doesn't contain MonadCancel?

We need it in several places, for example: https://github.com/typelevel/otel4s/pull/264/files#diff-36bdd6d74392dca0ddc0d9c6312f06c778e77a49e87fbc5c6fc966c936171f1cR208

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, do we really need both MonadCancel for F and G? If we have both fk and gk seems like we should be able to derive it.

Indeed

Copy link
Contributor

Choose a reason for hiding this comment

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

We need it in several places

oops, I worded that question really poorly. what's the reason it's an extension method and not in the trait?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That way I do not need to deal with recursive definitions, e.g.:

trait Tracer[F[_]] { self =>
  def mapK[G[_]](fk: F ~> G): Tracer[G] = 
    new Tracer {
      ...
      def mapK[Q[_]](fk1: G ~> Q): Tracer[Q] = 
        self.mapK(fk.andThen(fk1)))
    }
}

I like your approach with a dedicated type MappedK way more.

@NthPortal
Copy link
Contributor

If you need F ~> G and G ~> F, then the invariant nature means almost all interesting transformers are eliminated from use, OptionT, EitherT, Kleisli, IorT, etc.

liftOptionT requires a few copy-pastes, but compiles fine

I actually worked on a solution to both of these problems while working on #273; perhaps it can be combined with this PR for a union of the features of the two. I haven't quite finished the scaladocs, but I've pushed up my work as #284

@bpholt
Copy link
Member

bpholt commented Jul 24, 2023

FWIW, cats-tagless has some laws for FunctorK and InvariantK that might apply here. (It'd be nice to also provide no-import instances for those typeclasses where possible, but I understand that cats-tagless may not be stable enough to include as a dependency.)

Comment on lines +181 to +185
def translate[G[_]](fk: F ~> G, gk: G ~> F): Res[G] =
new Res[G] {
def span: Span[G] = res.span.mapK(fk)
def trace: G ~> G = res.trace.andThen(fk).compose(gk)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

is there a reason not to use Res.apply for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

trace at least should probably be a val because if you don't use it, nothing ends up traced, so it will be used in almost all cases

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, Res.apply fits better

@NthPortal
Copy link
Contributor

I like translate and mapK, and the derivation of MonadCancelThrow for translate is very clever as well. my main concern is the amount of code duplication that would be needed in order to support transformations to types other than OptionT

@NthPortal
Copy link
Contributor

@iRevive how do you want to reconcile our two PRs? do you want me to rewrite the parts of mine on top of yours, or yours on top of mine, or something else?

@iRevive
Copy link
Contributor Author

iRevive commented Aug 24, 2023

@NthPortal I prefer your version. It's more flexible, in my opinion.

I'll close this PR in favor of #284.

@iRevive iRevive closed this Aug 24, 2023
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.

6 participants