Skip to content

Instantly share code, notes, and snippets.

@tvoklov
Last active March 23, 2023 15:28
Show Gist options
  • Save tvoklov/187defe698fd8863d6c8699ea1ad34fe to your computer and use it in GitHub Desktop.
Save tvoklov/187defe698fd8863d6c8699ea1ad34fe to your computer and use it in GitHub Desktop.
import cats.effect.{Async, Clock, IO, IOApp, Ref}
import cats.syntax.all._
import java.time.LocalDateTime
trait Metric[F[_]] {
def modify(name: String, by: Int): F[Unit]
def set(name: String, value: Int): F[Unit]
}
object Metric {
def apply[F[_]: Metric]: Metric[F] = implicitly
}
trait Log[F[_]] {
def log(value: String): F[Unit]
}
object Log {
def apply[F[_]: Log]: Log[F] = implicitly
}
object Play extends IOApp.Simple {
def functionUsingMetric[F[_]: Async: Metric](param: Int) =
for {
_ <- Async[F].delay(println(s"setting metric to $param"))
_ <- Metric[F].set("metric 1", param)
_ <- Async[F].delay(println(s"increasing metric by $param"))
_ <- Metric[F].modify("metric 1", param)
} yield ()
def functionUsingLogger[F[_]: Async: Log](param: Int) =
for {
_ <- Async[F].delay(println(s"logging $param"))
_ <- Log[F].log(s"this is the param = $param")
} yield ()
def functionUsingBothTypeclasses[F[_]: Async: Metric: Log](param: Int) =
for {
_ <- functionUsingMetric(param)
_ <- functionUsingLogger(param)
} yield ()
implicit val runtimeForLog: Log[IO] = new Log[IO] {
override def log(value: String): IO[Unit] = for {
a <- IO.delay(LocalDateTime.now())
_ <- IO.println(s"$a : $value")
} yield ()
}
// prints out the metric modifications
def defaultRuntimeForMetric[F[_]: Async] = new Metric[F] {
override def modify(name: String, by: Int): F[Unit] =
Async[F].delay(println(s"modifying $name by $by"))
override def set(name: String, value: Int): F[Unit] =
Async[F].delay(println(s"setting $name to $value"))
}
def metricUsingIORef[F[_]: Async](ref: Ref[F, Map[String, Int]]) =
new Metric[F] {
override def modify(name: String, by: Int): F[Unit] =
ref.update(_.updatedWith(name) {
case Some(value) => Some(value + by)
case None => Some(by)
})
override def set(name: String, value: Int): F[Unit] =
ref.update(_.updated(name, value))
}
//
// pseudo-code, shows how to provide a runtime that takes parameters
//
// def metricSendingSomethingSomewhere[F[_]: Async](server: Client[F]) = new Metric[F] {
//
// override def modify(name: String, by: Int): F[Unit] = {
// val request: String = Request(
// method = POST,
// path = s"/$name/update?by=$by",
// )
//
// Client.send(request)
// }
//
// override def set(name: String, value: Int): F[Unit] = {
// val request: String = Request(
// method = POST,
// path = s"/$name/set?value=$value"
// )
//
// Client.send(request)
// }
// }
override def run: IO[Unit] =
for {
_ <- functionUsingLogger[IO](12)
_ <- {
// these implicit vals are going to be at the top
// everywhere else it's just going to use an F[_]
implicit val drfm: Metric[IO] = defaultRuntimeForMetric[IO]
functionUsingMetric[IO](15)
}
ref <- Ref[IO].of(Map.empty[String, Int])
_ <- {
implicit val rfm: Metric[IO] = metricUsingIORef(ref)
functionUsingBothTypeclasses[IO](20)
}
refAfter <- ref.get
_ <- IO.println(s"metric after updates: $refAfter")
} yield ()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment