type Setup[I] = I
type RunBenchmarks[I, R] = I => R
type Sink[R, O] = R => O
type Cleanup[I, C] = I => C
def setupRunSinkAndCleanup[F[_]: MonadBracket, I, O](
setup: F[I],
runBenchmarks: I => F[R],
sink: R => F[O],
cleanup: I => F[Unit]
): F[O] =
for {
start <- setup
out <- (for {
results <- runBenchmarks
output <- sink(results)
} yield output).guarantee(cleanup(start))
} yield out
final case class Log[L, A](
debug: L => A,
info: L => A,
error: L => A
)
def unlevelledLog[L, A](f: L => A): Log[L, A] =
Log(f, f, f)
implicit val logProfunctor: Profunctor[Log] =
new Profunctor[Log] {
def dimap[A, B, C, D]
(l: Log[A, B])(pre: C => A)(post: B => D)
: Log[C, D] = Log(
(c: C) => post(l.debug(pre(c))),
(c: C) => post(l.info(pre(c))),
(c: C) => post(l.error(pre(c)))
)
}
implicit val logCategory: Category[Log] =
new Category[Log] {
def id[A]: Log[A, A] =
Log(a => a, a => a, a => a)
def compose[A, B, C]
(f: Log[B, C], g: Log[A, B])
: Log[A, C] = Log(
(a: A) => f.debug(g.debug(a)),
(a: A) => f.info(g.info(a)),
(a: A) => f.error(g.error(a))
)
}
// exercise!
implicit val logMonad[L]: Monad[Log[L, ?]] = ???
val levelledLog = Log[String, String](
debug = msg => s"Debug: $msg",
info = msg => s"Info: $msg",
error = msg => s"Error: $msg"
)
val consoleLog: Log[String, IO[Unit]] =
levelledLog.rmap(console.putStrLn)
def logWithPlace(place: String, msg: String): String =
s"[$place]: $msg"
def consoleLoggerWithPlace: Log[(String, String), IO[Unit]] =
consoleLog.lmap { (logWithPlace _).tupled }
final class ReaderT[F[_], E, A](run: E => F[A])
def flatten[F[_], E, A]
(r: ReaderT[F, E, ReaderT[F, E, A]])
(implicit F: Monad[F]): ReaderT[F, E, A] = {
ReaderT((e: E) => r.run(e).flatMap(_.run(e)))
}
def local[F[_], E, NE, A]
(f: NE => E)
(rt: ReaderT[F, E, A]): ReaderT[F, NE, A] =
ReaderT(rt.run.compose(f))
def ask[F[_]: Applicative, E]: ReaderT[F, E, E] =
ReaderT((e: E) => e.pure[F])
def setupRunSinkAndCleanup[
F[_]: MonadBracket: MonadReader[?[_], String],
E,
I,
O
](
setup: F[I],
runBenchmarks: I => F[R],
sink: R => F[O],
cleanup: I => F[Unit]
): F[O] =
for {
start <- setup.local("Setup->" + _)
out <- (for {
results <- runBenchmarks.local("Run Benchmarks->" + _)
output <- sink(results).local("Output->" + _)
}).guarantee(cleanup(start).local("Cleanup->" + _)
} yield out
val consoleLogWithPlace: Log[(String, String), IO[Unit]]
// case class ReaderT[F[_], E, A](run: E => F[A])
val goal: Log[String, ReaderT[IO, String, Unit]] = ???
String => ReaderT[IO, String, Unit]
String => String => IO[Unit]
((String, String)) => IO[Unit]
def convertLogFunction[F[_]: Monad, A, E, B](
f: ((E, A)) => F[B]
): A => ReaderT[F, E, B] =
a => ReaderT[F, E, B](e => f((e, a)))
val readerLog: Log[String, ReaderT[IO, String, Unit]] =
Log(
debug = convertLogFunction(consoleLogWithPlace.debug),
info = convertLogFunction(consoleLogWithPlace.info),
error = convertLogFunction(consoleLogWithPlace.error)
)
final class LoggerAndScope[L, A](
logger: Log[L, A], scope: String
)
trait HasLogger[S, L, A] {
def getLogger(s: S[L, A]): Log[L, A]
def modifyLogger(f: Log[L, A] => Log[L, A]): S[L, A] => S[L, A]
}
trait HasScope[S] {
def getScope(s: S): String
def modifyScope(f: String => String): S => S
}
def setupRunSinkAndCleanup[
F[_]: MonadBracket: MonadReader[?[_], Env],
Env
I,
O
](
setup: F[I],
runBenchmarks: I => F[R],
sink: R => F[O],
cleanup: I => F[Unit]
)(
implicit S: HasScope[S]
): F[O] =
for {
start <- setup.local(_.modifyScope("Setup->" + _))
out <- (for {
results <- runBenchmarks(start)
.local(S.modifyScope("Run Benchmarks->" + _))
output <- sink(results)
.local(S.modifyScope("Output->" + _))
}).guarantee(cleanup(start)
.local(S.modifyScope("Cleanup->" + _))
} yield out