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

Fix #617: Make Resource covariant in F and A #731

Merged
merged 6 commits into from
Dec 31, 2019
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
10 changes: 9 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,15 @@ val mimaSettings = Seq(
exclude[DirectMissingMethodProblem]("cats.effect.IO.fromFuture"),
// Incompatible signatures should not cause linking problems.
exclude[IncompatibleSignatureProblem]("cats.effect.IO.ioParallel"),
exclude[IncompatibleSignatureProblem]("cats.effect.IOInstances.ioParallel")
exclude[IncompatibleSignatureProblem]("cats.effect.IOInstances.ioParallel"),
// Signature changes to make Resource covariant, should not cause linking problems. - https://github.com/typelevel/cats-effect/pull/731
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.use"),
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.flatMap"),
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.map"),
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.mapK"),
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.allocated"),
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.evalMap"),
exclude[IncompatibleSignatureProblem]("cats.effect.Resource.evalTap")
)
}
)
Expand Down
106 changes: 55 additions & 51 deletions core/shared/src/main/scala/cats/effect/Resource.scala
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ import scala.annotation.tailrec
* @tparam F the effect type in which the resource is allocated and released
* @tparam A the type of resource
*/
sealed abstract class Resource[F[_], A] {
sealed abstract class Resource[+F[_], +A] {
import Resource.{Allocate, Bind, Suspend}

/**
Expand All @@ -105,39 +105,39 @@ sealed abstract class Resource[F[_], A] {
* @param f the function to apply to the allocated resource
* @return the result of applying [F] to
*/
def use[B](f: A => F[B])(implicit F: Bracket[F, Throwable]): F[B] = {
def use[G[x] >: F[x], B](f: A => G[B])(implicit F: Bracket[G, Throwable]): G[B] = {
// Indirection for calling `loop` needed because `loop` must be @tailrec
def continue(current: Resource[F, Any], stack: List[Any => Resource[F, Any]]): F[Any] =
def continue(current: Resource[G, Any], stack: List[Any => Resource[G, Any]]): G[Any] =
loop(current, stack)

// Interpreter that knows how to evaluate a Resource data structure;
// Maintains its own stack for dealing with Bind chains
@tailrec def loop(current: Resource[F, Any], stack: List[Any => Resource[F, Any]]): F[Any] =
@tailrec def loop(current: Resource[G, Any], stack: List[Any => Resource[G, Any]]): G[Any] =
current match {
case Allocate(resource) =>
F.bracketCase(resource) {
case a: Allocate[G, Any] =>
F.bracketCase(a.resource) {
case (a, _) =>
stack match {
case Nil => f.asInstanceOf[Any => F[Any]](a)
case f0 :: xs => continue(f0(a), xs)
case Nil => f.asInstanceOf[Any => G[Any]](a)
case l => continue(l.head(a), l.tail)
}
} {
case ((_, release), ec) =>
release(ec)
}
case Bind(source, f0) =>
loop(source, f0.asInstanceOf[Any => Resource[F, Any]] :: stack)
case Suspend(resource) =>
resource.flatMap(continue(_, stack))
case b: Bind[G, _, Any] =>
loop(b.source, b.fs.asInstanceOf[Any => Resource[G, Any]] :: stack)
case s: Suspend[G, Any] =>
s.resource.flatMap(continue(_, stack))
}
loop(this.asInstanceOf[Resource[F, Any]], Nil).asInstanceOf[F[B]]
loop(this.asInstanceOf[Resource[G, Any]], Nil).asInstanceOf[G[B]]
}

/**
* Implementation for the `flatMap` operation, as described via the
* `cats.Monad` type class.
*/
def flatMap[B](f: A => Resource[F, B]): Resource[F, B] =
def flatMap[G[x] >: F[x], B](f: A => Resource[G, B]): Resource[G, B] =
Bind(this, f)

/**
Expand All @@ -146,14 +146,16 @@ sealed abstract class Resource[F[_], A] {
*
* This is the standard `Functor.map`.
*/
def map[B](f: A => B)(implicit F: Applicative[F]): Resource[F, B] =
flatMap(a => Resource.pure[F, B](f(a)))
def map[G[x] >: F[x], B](f: A => B)(implicit F: Applicative[G]): Resource[G, B] =
flatMap(a => Resource.pure[G, B](f(a)))

/**
* Given a natural transformation from `F` to `G`, transforms this
* Resource from effect `F` to effect `G`.
*/
def mapK[G[_]](f: F ~> G)(implicit B: Bracket[F, Throwable], D: Defer[G], G: Applicative[G]): Resource[G, A] =
def mapK[G[x] >: F[x], H[_]](
f: G ~> H
)(implicit B: Bracket[G, Throwable], D: Defer[H], G: Applicative[H]): Resource[H, A] =
this match {
case Allocate(resource) =>
Allocate(f(resource).map { case (a, r) => (a, r.andThen(u => f(u))) })
Expand Down Expand Up @@ -186,34 +188,34 @@ sealed abstract class Resource[F[_], A] {
* resource.
*
*/
def allocated(implicit F: Bracket[F, Throwable]): F[(A, F[Unit])] = {
def allocated[G[x] >: F[x], B >: A](implicit F: Bracket[G, Throwable]): G[(B, G[Unit])] = {
// Indirection for calling `loop` needed because `loop` must be @tailrec
def continue(current: Resource[F, Any], stack: List[Any => Resource[F, Any]], release: F[Unit]): F[(Any, F[Unit])] =
def continue(current: Resource[G, Any], stack: List[Any => Resource[G, Any]], release: G[Unit]): G[(Any, G[Unit])] =
loop(current, stack, release)

// Interpreter that knows how to evaluate a Resource data structure;
// Maintains its own stack for dealing with Bind chains
@tailrec def loop(current: Resource[F, Any],
stack: List[Any => Resource[F, Any]],
release: F[Unit]): F[(Any, F[Unit])] =
@tailrec def loop(current: Resource[G, Any],
stack: List[Any => Resource[G, Any]],
release: G[Unit]): G[(Any, G[Unit])] =
current match {
case Resource.Allocate(resource) =>
F.bracketCase(resource) {
case a: Allocate[G, Any] =>
F.bracketCase(a.resource) {
case (a, rel) =>
stack match {
case Nil => F.pure(a -> F.guarantee(rel(ExitCase.Completed))(release))
case f0 :: xs => continue(f0(a), xs, F.guarantee(rel(ExitCase.Completed))(release))
case Nil => F.pure(a -> F.guarantee(rel(ExitCase.Completed))(release))
case l => continue(l.head(a), l.tail, F.guarantee(rel(ExitCase.Completed))(release))
}
} {
case (_, ExitCase.Completed) =>
F.unit
case ((_, release), ec) =>
release(ec)
}
case Resource.Bind(source, f0) =>
loop(source, f0.asInstanceOf[Any => Resource[F, Any]] :: stack, release)
case Resource.Suspend(resource) =>
resource.flatMap(continue(_, stack, release))
case b: Bind[G, _, Any] =>
loop(b.source, b.fs.asInstanceOf[Any => Resource[G, Any]] :: stack, release)
case s: Suspend[G, Any] =>
s.resource.flatMap(continue(_, stack, release))
}

loop(this.asInstanceOf[Resource[F, Any]], Nil, F.unit).map {
Expand All @@ -226,14 +228,14 @@ sealed abstract class Resource[F[_], A] {
* Applies an effectful transformation to the allocated resource. Like a
* `flatMap` on `F[A]` while maintaining the resource context
*/
def evalMap[B](f: A => F[B])(implicit F: Applicative[F]): Resource[F, B] =
def evalMap[G[x] >: F[x], B](f: A => G[B])(implicit F: Applicative[G]): Resource[G, B] =
this.flatMap(a => Resource.liftF(f(a)))

/**
* Applies an effectful transformation to the allocated resource. Like a
* `flatTap` on `F[A]` while maintaining the resource context
*/
def evalTap[B](f: A => F[B])(implicit F: Applicative[F]): Resource[F, A] =
def evalTap[G[x] >: F[x], B](f: A => G[B])(implicit F: Applicative[G]): Resource[G, A] =
this.evalMap(a => f(a).as(a))
}

Expand Down Expand Up @@ -321,7 +323,7 @@ object Resource extends ResourceInstances with ResourcePlatform {
* @param fa the value to lift into a resource
*/
def liftF[F[_], A](fa: F[A])(implicit F: Applicative[F]): Resource[F, A] =
Resource.suspend(fa.map(a => Resource.pure(a)))
Resource.suspend(fa.map(a => Resource.pure[F, A](a)))

/**
* Lifts an applicative into a resource as a `FunctionK`. The resource has a no-op release.
Expand Down Expand Up @@ -359,17 +361,17 @@ object Resource extends ResourceInstances with ResourcePlatform {
def tailRecM[F[_], A, B](a: A)(f: A => Resource[F, Either[A, B]])(implicit F: Monad[F]): Resource[F, B] = {
def continue(r: Resource[F, Either[A, B]]): Resource[F, B] =
r match {
case Allocate(fea) =>
Suspend(fea.flatMap {
case a: Allocate[F, Either[A, B]] =>
Suspend(a.resource.flatMap[Resource[F, B]] {
case (Left(a), release) =>
release(Completed).map(_ => tailRecM(a)(f))
release(Completed).map(_ => tailRecM[F, A, B](a)(f))
case (Right(b), release) =>
F.pure(Allocate[F, B](F.pure((b, release))))
})
case Suspend(fr) =>
Suspend(fr.map(continue))
case Bind(source, fs) =>
Bind(source, AndThen(fs).andThen(continue))
case s: Suspend[F, Either[A, B]] =>
Suspend(s.resource.map(continue))
case b: Bind[F, _, Either[A, B]] =>
Bind(b.source, AndThen(b.fs).andThen(continue))
}

continue(f(a))
Expand All @@ -384,7 +386,7 @@ object Resource extends ResourceInstances with ResourcePlatform {
/**
* `Resource` data constructor that encodes the `flatMap` operation.
*/
final case class Bind[F[_], S, A](source: Resource[F, S], fs: S => Resource[F, A]) extends Resource[F, A]
final case class Bind[F[_], S, +A](source: Resource[F, S], fs: S => Resource[F, A]) extends Resource[F, A]

/**
* `Resource` data constructor that suspends the evaluation of another
Expand Down Expand Up @@ -418,13 +420,15 @@ abstract private[effect] class ResourceInstances0 {
def F = F0
}

implicit def catsEffectSemigroupForResource[F[_], A](implicit F0: Monad[F], A0: Semigroup[A]) =
implicit def catsEffectSemigroupForResource[F[_], A](implicit F0: Monad[F],
A0: Semigroup[A]): ResourceSemigroup[F, A] =
new ResourceSemigroup[F, A] {
def A = A0
def F = F0
}

implicit def catsEffectSemigroupKForResource[F[_], A](implicit F0: Monad[F], K0: SemigroupK[F]) =
implicit def catsEffectSemigroupKForResource[F[_], A](implicit F0: Monad[F],
K0: SemigroupK[F]): ResourceSemigroupK[F] =
new ResourceSemigroupK[F] {
def F = F0
def K = K0
Expand All @@ -444,29 +448,29 @@ abstract private[effect] class ResourceMonadError[F[_], E] extends ResourceMonad
case Right((a, release)) => (Right(a), release)
})
case Bind(source: Resource[F, Any], fs: (Any => Resource[F, A])) =>
Suspend(F.pure(source).map { source =>
Suspend(F.pure(source).map[Resource[F, Either[E, A]]] { source =>
Bind(attempt(source),
(r: Either[E, Any]) =>
r match {
case Left(error) => Resource.pure(Left(error))
case Left(error) => Resource.pure[F, Either[E, A]](Left(error))
case Right(s) => attempt(fs(s))
})
})
case Suspend(resource) =>
Suspend(resource.attempt.map {
case Left(error) => Resource.pure(Left(error))
case Right(fa: Resource[F, A]) => fa.attempt
case Left(error) => Resource.pure[F, Either[E, A]](Left(error))
case Right(fa: Resource[F, A]) => attempt(fa)
})
}

def handleErrorWith[A](fa: Resource[F, A])(f: E => Resource[F, A]): Resource[F, A] =
flatMap(attempt(fa)) {
case Right(a) => Resource.pure(a)
case Right(a) => Resource.pure[F, A](a)
case Left(e) => f(e)
}

def raiseError[A](e: E): Resource[F, A] =
Resource.applyCase(F.raiseError(e))
Resource.applyCase[F, A](F.raiseError(e))
}

abstract private[effect] class ResourceMonad[F[_]] extends Monad[Resource[F, *]] {
Expand All @@ -476,7 +480,7 @@ abstract private[effect] class ResourceMonad[F[_]] extends Monad[Resource[F, *]]
fa.map(f)

def pure[A](a: A): Resource[F, A] =
Resource.applyCase(F.pure((a, _ => F.unit)))
Resource.applyCase[F, A](F.pure((a, _ => F.unit)))

def flatMap[A, B](fa: Resource[F, A])(f: A => Resource[F, B]): Resource[F, B] =
fa.flatMap(f)
Expand All @@ -488,7 +492,7 @@ abstract private[effect] class ResourceMonad[F[_]] extends Monad[Resource[F, *]]
abstract private[effect] class ResourceMonoid[F[_], A] extends ResourceSemigroup[F, A] with Monoid[Resource[F, A]] {
implicit protected def A: Monoid[A]

def empty: Resource[F, A] = Resource.pure(A.empty)
def empty: Resource[F, A] = Resource.pure[F, A](A.empty)
}

abstract private[effect] class ResourceSemigroup[F[_], A] extends Semigroup[Resource[F, A]] {
Expand Down