diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 8ea39bb..ba0f116 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -3,7 +3,7 @@ import sbt._ object Dependencies { val scalatest = "org.scalatest" %% "scalatest" % "3.2.9" - val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "2.7.2" - val random = "com.evolutiongaming" %% "random" % "0.1.1" - val `cats-effect` = "org.typelevel" %% "cats-effect" % "2.5.1" + val `cats-helper` = "com.evolutiongaming" %% "cats-helper" % "3.0.1" + val random = "com.evolutiongaming" %% "random" % "1.0.0" + val `cats-effect` = "org.typelevel" %% "cats-effect" % "3.3.4" } \ No newline at end of file diff --git a/src/main/scala/com/evolutiongaming/retry/Retry.scala b/src/main/scala/com/evolutiongaming/retry/Retry.scala index 2eef531..5695569 100644 --- a/src/main/scala/com/evolutiongaming/retry/Retry.scala +++ b/src/main/scala/com/evolutiongaming/retry/Retry.scala @@ -1,14 +1,13 @@ package com.evolutiongaming.retry -import java.time.Instant - import cats.arrow.FunctionK -import cats.effect.{Clock, Timer} +import cats.effect.{Clock, GenTemporal, Temporal} import cats.implicits._ -import cats.{MonadError, ~>} +import cats.~> import com.evolutiongaming.catshelper.ClockHelper._ -import com.evolutiongaming.catshelper.{Log, MonadThrowable} +import com.evolutiongaming.catshelper.Log +import java.time.Instant import scala.concurrent.duration._ trait Retry[F[_]] { @@ -26,10 +25,10 @@ object Retry { } - def apply[F[_] : Timer, E]( + def apply[F[_], E]( strategy: Strategy, onError: OnError[F, E])(implicit - F: MonadError[F, E] + F: GenTemporal[F, E] ): Retry[F] = { type S = (Status, Strategy) @@ -42,7 +41,7 @@ object Retry { } for { - now <- Clock[F].instant + now <- Clock[F].realTimeInstant decision = strategy(status, now) result <- decision match { case Decision.GiveUp => @@ -54,7 +53,7 @@ object Retry { case Decision.Retry(delay, status, decide) => for { _ <- onError1(status, decision) - _ <- Timer[F].sleep(delay) + _ <- Temporal[F].sleep(delay) } yield { (status.plus(delay), decide).asLeft[A] } @@ -79,9 +78,9 @@ object Retry { } - def apply[F[_] : Timer, E]( + def apply[F[_], E]( strategy: Strategy)(implicit - F: MonadError[F, E] + F: GenTemporal[F, E] ): Retry[F] = { apply(strategy, OnError.empty[F, E]) } @@ -120,16 +119,14 @@ object Retry { def retry[E]( strategy: Strategy, onError: OnError[F, E])(implicit - F: MonadError[F, E], - timer: Timer[F] + F: GenTemporal[F, E] ): F[A] = { Retry(strategy, onError).apply(self) } def retry[E]( strategy: Strategy)(implicit - F: MonadError[F, E], - timer: Timer[F] + F: GenTemporal[F, E] ): F[A] = { self.retry(strategy, OnError.empty[F, E]) } @@ -137,8 +134,7 @@ object Retry { def retry( strategy: Strategy, log: Log[F])(implicit - F: MonadThrowable[F], - timer: Timer[F] + F: GenTemporal[F, Throwable] ): F[A] = { self.retry(strategy, OnError.fromLog(log)) } diff --git a/src/test/scala/com/evolutiongaming/retry/RetrySpec.scala b/src/test/scala/com/evolutiongaming/retry/RetrySpec.scala index 3ffc960..c65553b 100644 --- a/src/test/scala/com/evolutiongaming/retry/RetrySpec.scala +++ b/src/test/scala/com/evolutiongaming/retry/RetrySpec.scala @@ -2,14 +2,16 @@ package com.evolutiongaming.retry import cats._ import cats.arrow.FunctionK -import cats.effect.{Clock, Timer} +import cats.effect.kernel._ import cats.implicits._ import com.evolutiongaming.random.Random import com.evolutiongaming.retry.Retry._ import com.evolutiongaming.retry.Retry.implicits._ -import java.time.Instant import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers + +import java.time.Instant +import java.util.concurrent.TimeUnit import scala.annotation.tailrec import scala.concurrent.duration._ @@ -307,59 +309,72 @@ object RetrySpec { val InitialTime = System.currentTimeMillis() - implicit val MonadErrorStateT: MonadError[StateT, Error] = new MonadError[StateT, Error] { + implicit val GenTemporalStateT: GenTemporal[StateT, Error] = new GenTemporal[StateT, Error] { + override def sleep(time: FiniteDuration): StateT[Error] = + StateT { s => (s.sleep(time), ().asRight) } - def flatMap[A, B](fa: StateT[A])(f: A => StateT[B]) = { - StateT[B] { s => - val (s1, a) = fa.run(s) - a.fold(a => (s1, a.asLeft), a => f(a).run(s1)) - } - } + override def ref[A](a: A): StateT[Ref[StateT, A]] = ??? // there's no Sync for Id - def tailRecM[A, B](a: A)(f: A => StateT[Either[A, B]]) = { + override def deferred[A]: StateT[Deferred[StateT, A]] = ??? // there's no Sync for Id - @tailrec - def apply(s: State, a: A): (State, FE[B]) = { - val (s1, b) = f(a).run(s) - b match { - case Right(Right(b)) => (s1, b.asRight) - case Right(Left(b)) => apply(s1, b) - case Left(b) => (s1, b.asLeft) - } - } + override def unique: StateT[Unique.Token] = + StateT { s => (s, new Unique.Token().asRight)} - StateT { s => apply(s, a) } - } + override def pure[A](x: A): StateT[A] = + StateT { s => (s, x.asRight)} - def raiseError[A](e: Error) = { - StateT { s => (s, e.asLeft) } - } + override def monotonic: StateT[FiniteDuration] = + StateT { s => (s, 0.seconds.asRight)} - def handleErrorWith[A](fa: StateT[A])(f: Error => StateT[A]) = { + override def realTime: StateT[FiniteDuration] = { StateT { s => - val (s1, a) = fa.run(s) - a.fold(a => f(a).run(s1), a => (s1, a.asRight)) + val delay = s.delays.map(_.toMillis).sum + (s, FiniteDuration(InitialTime + delay, TimeUnit.MILLISECONDS).asRight) } } - def pure[A](a: A) = StateT { s => (s, a.asRight) } - } + override def start[A](fa: StateT[A]): StateT[Fiber[StateT, Error, A]] = ??? // can't be expressed with Id + override def never[A]: StateT[A] = ??? // can't be expressed with Id - implicit val TimerStateT: Timer[StateT] = new Timer[StateT] { + override def cede: StateT[Error] = StateT { s => (s, ().asLeft)} - val clock = new Clock[StateT] { + override def forceR[A, B](fa: StateT[A])(fb: StateT[B]): StateT[B] = ??? - def realTime(unit: TimeUnit) = StateT { s => - val delay = s.delays.map(_.toMillis).sum - (s, (InitialTime + delay).asRight) + override def uncancelable[A](body: Poll[StateT] => StateT[A]): StateT[A] = body(new Poll[StateT] { + override def apply[X](fa: StateT[X]): StateT[X] = fa + }) + + override def canceled: StateT[Error] = ??? + + override def onCancel[A](fa: StateT[A], fin: StateT[Error]): StateT[A] = fa // can't be expressed with Id + + override def raiseError[A](e: Error): StateT[A] = StateT { s => (s, e.asLeft) } + + override def handleErrorWith[A](fa: StateT[A])(f: Error => StateT[A]): StateT[A] = + StateT { s => + val (s1, a) = fa.run(s) + a.fold(a => f(a).run(s1), a => (s1, a.asRight)) } - def monotonic(unit: TimeUnit) = StateT { s => (s, 0L.asRight) } - } + override def flatMap[A, B](fa: StateT[A])(f: A => StateT[B]): StateT[B] = + StateT[B] { s => + val (s1, a) = fa.run(s) + a.fold(a => (s1, a.asLeft), a => f(a).run(s1)) + } + + override def tailRecM[A, B](a: A)(f: A => StateT[Either[A, B]]): StateT[B] = { + @tailrec + def apply(s: State, a: A): (State, FE[B]) = { + val (s1, b) = f(a).run(s) + b match { + case Right(Right(b)) => (s1, b.asRight) + case Right(Left(b)) => apply(s1, b) + case Left(b) => (s1, b.asLeft) + } + } - def sleep(duration: FiniteDuration) = { - StateT { s => (s.sleep(duration), ().asRight) } + StateT { s => apply(s, a) } } }