From 9763d4844b60e83b0fd9ac62e7d6eedda66fce38 Mon Sep 17 00:00:00 2001 From: Daniel Urban Date: Sun, 20 May 2018 12:48:07 +0200 Subject: [PATCH] Bracket instance for Kleisli --- build.sbt | 5 +- .../src/main/scala/cats/effect/Async.scala | 5 +- .../src/main/scala/cats/effect/Bracket.scala | 46 +++++++++++++++++++ .../main/scala/cats/effect/Concurrent.scala | 7 +-- .../src/main/scala/cats/effect/Sync.scala | 29 +++--------- .../scala/cats/effect/InstancesTests.scala | 2 + 6 files changed, 65 insertions(+), 29 deletions(-) diff --git a/build.sbt b/build.sbt index 6cabd0ca3d..42da1c15b6 100644 --- a/build.sbt +++ b/build.sbt @@ -211,7 +211,10 @@ val mimaSettings = Seq( exclude[DirectMissingMethodProblem]("cats.effect.internals.IOFrame.errorHandler"), // New stuff exclude[ReversedMissingMethodProblem]("cats.effect.internals.IOConnection.tryReactivate"), - exclude[DirectMissingMethodProblem]("cats.effect.internals.IOCancel#RaiseCancelable.this") + exclude[DirectMissingMethodProblem]("cats.effect.internals.IOCancel#RaiseCancelable.this"), + exclude[IncompatibleTemplateDefProblem]("cats.effect.Concurrent$KleisliConcurrent"), + exclude[IncompatibleTemplateDefProblem]("cats.effect.Sync$KleisliSync"), + exclude[IncompatibleTemplateDefProblem]("cats.effect.Async$KleisliAsync") ) }) diff --git a/core/shared/src/main/scala/cats/effect/Async.scala b/core/shared/src/main/scala/cats/effect/Async.scala index ec56b19534..5d31e0a32b 100644 --- a/core/shared/src/main/scala/cats/effect/Async.scala +++ b/core/shared/src/main/scala/cats/effect/Async.scala @@ -270,8 +270,9 @@ object Async { WriterT.liftF(F.async(k))(L, FA) } - private[effect] trait KleisliAsync[F[_], R] extends Async[Kleisli[F, R, ?]] - with Sync.KleisliSync[F, R] { + private[effect] abstract class KleisliAsync[F[_], R] + extends Sync.KleisliSync[F, R] + with Async[Kleisli[F, R, ?]] { override protected implicit def F: Async[F] diff --git a/core/shared/src/main/scala/cats/effect/Bracket.scala b/core/shared/src/main/scala/cats/effect/Bracket.scala index d68975f38b..057226db9a 100644 --- a/core/shared/src/main/scala/cats/effect/Bracket.scala +++ b/core/shared/src/main/scala/cats/effect/Bracket.scala @@ -17,6 +17,8 @@ package cats package effect +import cats.data.Kleisli + /** * An extension of `MonadError` exposing the `bracket` operation, * a generalized abstracted pattern of safe resource acquisition and @@ -140,5 +142,49 @@ object ExitCase { } object Bracket { + def apply[F[_], E](implicit ev: Bracket[F, E]): Bracket[F, E] = ev + + /** + * [[Bracket]] instance built for `cats.data.Kleisli` values initialized + * with any `F` data type that also implements `Bracket`. + */ + implicit def catsKleisliBracket[F[_], R, E](implicit ev: Bracket[F, E]): Bracket[Kleisli[F, R, ?], E] = + new KleisliBracket[F, R, E] { def F = ev } + + private[effect] abstract class KleisliBracket[F[_], R, E] extends Bracket[Kleisli[F, R, ?], E] { + + protected implicit def F: Bracket[F, E] + + // NB: preferably we'd inherit things from `cats.data.KleisliApplicativeError`, + // but we can't, because it's `private[data]`, so we have to delegate. + private[this] final val kleisliMonadError: MonadError[Kleisli[F, R, ?], E] = + Kleisli.catsDataMonadErrorForKleisli + + def pure[A](x: A): Kleisli[F, R, A] = + kleisliMonadError.pure(x) + + def handleErrorWith[A](fa: Kleisli[F, R, A])(f: E => Kleisli[F, R, A]): Kleisli[F, R, A] = + kleisliMonadError.handleErrorWith(fa)(f) + + def raiseError[A](e: E): Kleisli[F, R, A] = + kleisliMonadError.raiseError(e) + + def flatMap[A, B](fa: Kleisli[F, R, A])(f: A => Kleisli[F, R, B]): Kleisli[F, R, B] = + kleisliMonadError.flatMap(fa)(f) + + def tailRecM[A, B](a: A)(f: A => Kleisli[F, R, Either[A, B]]): Kleisli[F, R, B] = + kleisliMonadError.tailRecM(a)(f) + + def bracketCase[A, B](acquire: Kleisli[F, R, A]) + (use: A => Kleisli[F, R, B]) + (release: (A, ExitCase[E]) => Kleisli[F, R, Unit]): Kleisli[F, R, B] = { + + Kleisli { r => + F.bracketCase(acquire.run(r))(a => use(a).run(r)) { (a, br) => + release(a, br).run(r) + } + } + } + } } diff --git a/core/shared/src/main/scala/cats/effect/Concurrent.scala b/core/shared/src/main/scala/cats/effect/Concurrent.scala index ac6c766e5a..1cb22a8cd1 100644 --- a/core/shared/src/main/scala/cats/effect/Concurrent.scala +++ b/core/shared/src/main/scala/cats/effect/Concurrent.scala @@ -551,7 +551,7 @@ object Concurrent { F.pure(Some(Right((fiberT[A](fiberA), r)))) } }) - + def uncancelable[A](fa: OptionT[F, A]): OptionT[F, A] = OptionT(F.uncancelable(fa.value)) @@ -634,8 +634,9 @@ object Concurrent { Fiber(WriterT(fiber.join), WriterT.liftF(fiber.cancel)) } - private[effect] trait KleisliConcurrent[F[_], R] extends Concurrent[Kleisli[F, R, ?]] - with Async.KleisliAsync[F, R] { + private[effect] abstract class KleisliConcurrent[F[_], R] + extends Async.KleisliAsync[F, R] + with Concurrent[Kleisli[F, R, ?]] { override protected implicit def F: Concurrent[F] // Needed to drive static checks, otherwise the diff --git a/core/shared/src/main/scala/cats/effect/Sync.scala b/core/shared/src/main/scala/cats/effect/Sync.scala index 304a2397c9..ff29beea35 100644 --- a/core/shared/src/main/scala/cats/effect/Sync.scala +++ b/core/shared/src/main/scala/cats/effect/Sync.scala @@ -228,36 +228,19 @@ object Sync { WriterT(F.suspend(thunk.run)) } - private[effect] trait KleisliSync[F[_], R] extends Sync[Kleisli[F, R, ?]] { - protected implicit def F: Sync[F] + private[effect] abstract class KleisliSync[F[_], R] + extends Bracket.KleisliBracket[F, R, Throwable] + with Sync[Kleisli[F, R, ?]] { - def pure[A](x: A): Kleisli[F, R, A] = - Kleisli.pure(x) + protected implicit override def F: Sync[F] - def handleErrorWith[A](fa: Kleisli[F, R, A])(f: Throwable => Kleisli[F, R, A]): Kleisli[F, R, A] = + override def handleErrorWith[A](fa: Kleisli[F, R, A])(f: Throwable => Kleisli[F, R, A]): Kleisli[F, R, A] = Kleisli { r => F.suspend(F.handleErrorWith(fa.run(r))(e => f(e).run(r))) } - def raiseError[A](e: Throwable): Kleisli[F, R, A] = - Kleisli.liftF(F.raiseError(e)) - - def flatMap[A, B](fa: Kleisli[F, R, A])(f: A => Kleisli[F, R, B]): Kleisli[F, R, B] = + override def flatMap[A, B](fa: Kleisli[F, R, A])(f: A => Kleisli[F, R, B]): Kleisli[F, R, B] = Kleisli { r => F.suspend(fa.run(r).flatMap(f.andThen(_.run(r)))) } - def tailRecM[A, B](a: A)(f: A => Kleisli[F, R, Either[A, B]]): Kleisli[F, R, B] = - Kleisli.catsDataMonadForKleisli[F, R].tailRecM(a)(f) - def suspend[A](thunk: => Kleisli[F, R, A]): Kleisli[F, R, A] = Kleisli(r => F.suspend(thunk.run(r))) - - def bracketCase[A, B](acquire: Kleisli[F, R, A]) - (use: A => Kleisli[F, R, B]) - (release: (A, ExitCase[Throwable]) => Kleisli[F, R, Unit]): Kleisli[F, R, B] = { - - Kleisli { r => - F.bracketCase(acquire.run(r))(a => use(a).run(r)) { (a, br) => - release(a, br).run(r) - } - } - } } } diff --git a/laws/shared/src/test/scala/cats/effect/InstancesTests.scala b/laws/shared/src/test/scala/cats/effect/InstancesTests.scala index 4374f23e9c..72d50a5375 100644 --- a/laws/shared/src/test/scala/cats/effect/InstancesTests.scala +++ b/laws/shared/src/test/scala/cats/effect/InstancesTests.scala @@ -33,6 +33,8 @@ class InstancesTests extends BaseTestsSuite { checkAllAsync("Kleisli[IO, ?]", implicit ec => ConcurrentTests[Kleisli[IO, Int, ?]].concurrent[Int, Int, Int]) + checkAllAsync("Kleisli[IO, ?]", + implicit ec => BracketTests[Kleisli[IO, Int, ?], Throwable].bracket[Int, Int, Int]) checkAllAsync("EitherT[IO, Throwable, ?]", implicit ec => ConcurrentEffectTests[EitherT[IO, Throwable, ?]].concurrentEffect[Int, Int, Int])