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

PM-2906: Contravariant tracing #3

Merged
merged 5 commits into from
Mar 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion build.sc
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class MetronomeModule(val crossScalaVersion: String) extends CrossScalaModule {
versionControl = VersionControl.github("input-output-hk", "metronome"),
// Add yourself if you make a PR!
developers = Seq(
Developer("aakoshh", "Akosh Farkash", "https://github.com/aakoshh")
Developer("aakoshh", "Akosh Farkash", "https://github.com/aakoshh"),
Developer("lemastero", "Piotr Paradzinski", "https://github.com/lemastero")
)
)
}
Expand Down Expand Up @@ -98,6 +99,8 @@ class MetronomeModule(val crossScalaVersion: String) extends CrossScalaModule {
object tracing extends SubModule with Publishing {
override def description: String =
"Abstractions for contravariant tracing."

def scalacPluginIvyDeps = Agg(ivy"org.typelevel:::kind-projector:0.11.3")
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, cool, I was wondering yesterday how to do this with mill, cheers!

}

/** Additional crypto utilities such as threshold signature. */
Expand Down
105 changes: 105 additions & 0 deletions metronome/tracing/src/main/scala/io/iohk/metronome/tracer/Tracer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@

import cats.{Applicative, Contravariant, FlatMap, Id, Monad, Monoid, Show, ~>}

/**
* Contravariant tracer
*
* Ported from https://github.com/input-output-hk/contra-tracer/blob/master/src/Control/Tracer.hs
*/
trait Tracer[F[_], -A] extends Function[A, F[Unit]]

object Tracer {

/**
* If you know how to trace A and
* - how to enrich A to get value B
* - how to forget some details about B to get A
* then you can create Tracer for B
*/
implicit def contraTracer[F[_]]: Contravariant[Tracer[F, *]] =
new Contravariant[Tracer[F, *]] {
override def contramap[A, B](fa: Tracer[F, A])(f: B => A): Tracer[F, B] =
a => fa(f(a))
}

def noOpTracer[M[_], A](implicit MA: Applicative[M]): Tracer[M, A] =
_ => MA.pure(())

implicit def monoidTracer[F[_], S](implicit MA: Applicative[F]): Monoid[Tracer[F, S]] =
new Monoid[Tracer[F, S]] {

/** Run sequentially two tracers */
override def combine(a1: Tracer[F, S], a2: Tracer[F, S]): Tracer[F, S] =
s => MA.productR(a1(s))(a2(s))

override def empty: Tracer[F, S] = noOpTracer
}

/** Trace value a using tracer tracer */
def traceWith[F[_], A](tracer: Tracer[F, A], a: A): F[Unit] = tracer(a)

/** contravariant Kleisli composition:
* if you can:
* - produce effect M[B] from A
* - trace B's
* then you can trace A's
*/
def contramapM[F[_], A, B](f: A => F[B], tracer: Tracer[F, B])(implicit MM: FlatMap[F]): Tracer[F, A] =
(a: A) => MM.flatMap(f(a))(tracer)

/** change the effect F to G using natural transformation nat */
def natTracer[F[_], G[_], A](nat: F ~> G, tracer: Tracer[F, A]): Tracer[G, A] =
a => nat(tracer(a))

/** filter out values to trace if they do not pass predicate p */
def condTracing[F[_], A](p: A => Boolean, tr: Tracer[F, A])(implicit FM: Applicative[F]): Tracer[F, A] =
(a: A) =>
if (p(a)) tr(a)
else FM.pure(())

/** filter out values that was send to trace using side effecting predicate */
def condTracingM[F[_], A](p: F[A => Boolean], tr: Tracer[F, A])(implicit FM: Monad[F]): Tracer[F, A] =
a =>
FM.flatMap(p) { pa =>
if (pa(a)) tr(a)
else FM.pure(())
}

def showTracing[F[_], A](
tracer: Tracer[F, String]
)(implicit S: Show[A], C: Contravariant[Tracer[F, *]]): Tracer[F, A] =
C.contramap(tracer)(S.show)

def traceAll[A, B](f: B => List[A], t: Tracer[Id, A]): Tracer[Id, B] =
event => f(event).foreach(t)
}

object TracerSyntax {

implicit class TracerOps[F[_], A](val tracer: Tracer[F, A]) extends AnyVal {

/** Trace value a using tracer */
def trace(a: A): F[Unit] = tracer(a)

/** contravariant Kleisli composition:
* if you can:
* - produce effect M[B] from A
* - trace B's
* then you can trace A's
*/
def >=>[B](f: B => F[A])(implicit MM: FlatMap[F]): Tracer[F, B] =
Tracer.contramapM(f, tracer)

def nat[G[_]](nat: F ~> G): Tracer[G, A] =
Tracer.natTracer(nat, tracer)

def filter(p: A => Boolean)(implicit FM: Applicative[F]): Tracer[F, A] =
Tracer.condTracing[F, A](p, tracer)

def filterNot(p: A => Boolean)(implicit FM: Applicative[F]): Tracer[F, A] =
filter(a => !p(a))

def filterM(p: F[A => Boolean])(implicit FM: Monad[F]): Tracer[F, A] =
Tracer.condTracingM(p, tracer)
}
}