Skip to content

Commit

Permalink
Simplify module structure and introduce alleycats-retry (#132)
Browse files Browse the repository at this point in the history
* Simplify module structure and introduce alleycats-retry

* Move retrying to alleycats-retry

* Fix oversights
  • Loading branch information
LukaJCB authored Dec 13, 2019
1 parent 5e955f9 commit 91b7d9a
Show file tree
Hide file tree
Showing 14 changed files with 111 additions and 220 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

A library for retrying actions that can fail.

Designed to work with [cats](https://typelevel.org/cats/) and (optionally)
[cats-effect](https://typelevel.org/cats-effect/) or [Monix](https://monix.io/).
Designed to work with [cats](https://typelevel.org/cats/) and [cats-effect](https://typelevel.org/cats-effect/) or [Monix](https://monix.io/).

Inspired by the [retry Haskell
package](https://hackage.haskell.org/package/retry).
Expand Down
53 changes: 23 additions & 30 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ val commonSettings = Seq(
)

val moduleSettings = commonSettings ++ Seq(
moduleName := s"cats-retry-${name.value}",
scalacOptions ++= Seq(
"-Xfuture",
"-Ywarn-dead-code",
Expand All @@ -58,6 +57,7 @@ val moduleSettings = commonSettings ++ Seq(
)

val catsVersion = "2.0.0"
val catsEffectVersion = "2.0.0"
val scalatestVersion = "3.1.0"
val scalaTestPlusVersion = "3.1.0.0-RC2"
val scalacheckVersion = "1.14.2"
Expand All @@ -67,9 +67,13 @@ val core = crossProject(JVMPlatform, JSPlatform)
.in(file("modules/core"))
.settings(moduleSettings)
.settings(
name := "cats-retry",
crossScalaVersions := scalaVersions,
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % catsVersion,
"org.typelevel" %%% "cats-effect" % catsEffectVersion,
"org.scalatest" %%% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test,
"org.typelevel" %%% "cats-laws" % catsVersion % Test,
"org.scalatest" %%% "scalatest" % scalatestVersion % Test,
"org.scalatestplus" %%% "scalatestplus-scalacheck" % scalaTestPlusVersion % Test,
Expand All @@ -80,42 +84,30 @@ val core = crossProject(JVMPlatform, JSPlatform)
val coreJVM = core.jvm
val coreJS = core.js

val catsEffect = crossProject(JVMPlatform, JSPlatform)
.in(file("modules/cats-effect"))
val alleycatsRetry = crossProject(JVMPlatform, JSPlatform)
.in(file("modules/alleycats"))
.jvmConfigure(_.dependsOn(coreJVM))
.jsConfigure(_.dependsOn(coreJS))
.settings(moduleSettings)
.settings(
name := "alleycats-retry",
crossScalaVersions := scalaVersions,
name := "cats-effect",
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-effect" % "2.0.0",
"org.scalatest" %%% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test
)
)
val catsEffectJVM = catsEffect.jvm
val catsEffectJS = catsEffect.js

val monix = crossProject(JVMPlatform, JSPlatform)
.in(file("modules/monix"))
.jvmConfigure(_.dependsOn(coreJVM))
.jsConfigure(_.dependsOn(coreJS))
.settings(moduleSettings)
.settings(
crossScalaVersions := List(scalaVersion212, scalaVersion211),
libraryDependencies ++= Seq(
"io.monix" %%% "monix" % "3.1.0",
"org.scalatest" %%% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test
"org.scalatest" %%% "scalatest" % scalatestVersion % Test,
"org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test,
"org.typelevel" %%% "cats-laws" % catsVersion % Test,
"org.scalatest" %%% "scalatest" % scalatestVersion % Test,
"org.scalatestplus" %%% "scalatestplus-scalacheck" % scalaTestPlusVersion % Test,
"org.typelevel" %%% "discipline-scalatest" % disciplineVersion % Test,
"org.scalacheck" %%% "scalacheck" % scalacheckVersion % Test
)
)
val monixJVM = monix.jvm
val monixJS = monix.js
val alleycatsJVM = alleycatsRetry.jvm
val alleycatsJS = alleycatsRetry.js

val docs = project
.in(file("modules/docs"))
.dependsOn(coreJVM, catsEffectJVM, monixJVM)
.dependsOn(coreJVM, alleycatsJVM)
.enablePlugins(MicrositesPlugin, BuildInfoPlugin)
.settings(moduleSettings)
.settings(
Expand All @@ -125,6 +117,9 @@ val docs = project
addCompilerPlugin(
"org.typelevel" %% "kind-projector" % "0.11.0" cross CrossVersion.full
),
libraryDependencies ++= Seq(
"io.monix" %%% "monix" % "3.1.0"
),
crossScalaVersions := Nil,
buildInfoPackage := "retry",
publishArtifact := false,
Expand All @@ -148,10 +143,8 @@ val root = project
.aggregate(
coreJVM,
coreJS,
catsEffectJVM,
catsEffectJS,
monixJVM,
monixJS,
alleycatsJVM,
alleycatsJS,
docs
)
.settings(commonSettings)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
package retry

import java.util.concurrent.Executors
package alleycats

import cats.{Eval, Id}

import scala.concurrent.duration.FiniteDuration
import scala.concurrent.{Future, Promise}
import java.util.concurrent.ThreadFactory

trait Sleep[M[_]] {
def sleep(delay: FiniteDuration): M[Unit]
}

object Sleep {
def apply[M[_]](implicit sleep: Sleep[M]): Sleep[M] = sleep
import java.util.concurrent.{ThreadFactory, Executors}

object instances {
implicit val threadSleepId: Sleep[Id] = new Sleep[Id] {
def sleep(delay: FiniteDuration): Id[Unit] = Thread.sleep(delay.toMillis)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package retry
package retry.alleycats

import cats.instances.future._
import org.scalatest.flatspec.AnyFlatSpec
import retry._
import retry.alleycats.instances._

import scala.collection.mutable.ArrayBuffer
import scala.concurrent.duration._
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package retry
package alleycats

import cats.{Id, Monad}

object syntax {
def retrying[A](
policy: RetryPolicy[Id],
wasSuccessful: A => Boolean,
onFailure: (A, RetryDetails) => Unit
)(
action: => A
)(
implicit
M: Monad[Id],
S: Sleep[Id]
): A =
retryingM[A][Id](policy, wasSuccessful, onFailure)(action)
}
11 changes: 0 additions & 11 deletions modules/core/js/src/main/scala/retry/Sleep.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package retry

import cats.effect.Timer
import scala.concurrent.duration.FiniteDuration

import cats.effect.Timer
trait Sleep[M[_]] {
def sleep(delay: FiniteDuration): M[Unit]
}

object Sleep {
def apply[M[_]](implicit sleep: Sleep[M]): Sleep[M] = sleep

trait CatsEffect {
implicit def sleepUsingTimer[F[_]](implicit timer: Timer[F]): Sleep[F] =
new Sleep[F] {
def sleep(delay: FiniteDuration): F[Unit] = timer.sleep(delay)
}
}

object CatsEffect extends CatsEffect
13 changes: 0 additions & 13 deletions modules/core/shared/src/main/scala/retry/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@ import cats.syntax.flatMap._
import scala.concurrent.duration.FiniteDuration

package object retry {
def retrying[A](
policy: RetryPolicy[Id],
wasSuccessful: A => Boolean,
onFailure: (A, RetryDetails) => Unit
)(
action: => A
)(
implicit
M: Monad[Id],
S: Sleep[Id]
): A =
retryingM[A][Id](policy, wasSuccessful, onFailure)(action)

def retryingM[A] = new RetryingPartiallyApplied[A]

private[retry] class RetryingPartiallyApplied[A] {
Expand Down
55 changes: 4 additions & 51 deletions modules/docs/src/main/mdoc/docs/combinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,58 +8,12 @@ title: Combinators
The library offers a few slightly different ways to wrap your operations with
retries.

## `retrying`

This is useful when you are not working in a monadic context. You have an
operation that simply returns a value of some type `A`, and you want to retry
until it returns a value that you are happy with.

To use `retrying`, you pass in a predicate that decides whether you are
happy with the result or you want to retry.

The API looks like this:

```scala
def retrying[A](policy: RetryPolicy[Id],
wasSuccessful: A => Boolean,
onFailure: (A, RetryDetails) => Unit)
(action: => A): A
```

You need to pass in:

* a retry policy
* a predicate that decides whether the operation was successful
* a failure handler, often used for logging
* the operation that you want to wrap with retries

For example, let's keep rolling a die until we get a six.

```scala mdoc:reset-class
import cats.Id
import retry._
import scala.concurrent.duration._

val policy = RetryPolicies.constantDelay[Id](10.milliseconds)

val predicate = (_: Int) == 6

def onFailure(failedValue: Int, details: RetryDetails): Unit = {
println(s"Rolled a $failedValue, retrying ...")
}

val loadedDie = util.LoadedDie(2, 5, 4, 1, 3, 2, 6)

retrying(policy, predicate, onFailure){
loadedDie.roll()
}
```

## `retryingM`

This is similar to `retrying`, but is useful when you are working in an
arbitrary `Monad` that is not a `MonadError`. Your operation doesn't throw
errors, but you want to retry until it returns a value that you are happy with.
To use `retryingM`, you pass in a predicate that decides whether you are
happy with the result or you want to retry.
It is useful when you are working in an arbitrary `Monad` that is not a `MonadError`.
Your operation doesn't throw errors, but you want to retry until it returns a value that you are happy with.

The API (modulo some type-inference trickery) looks like this:

Expand All @@ -84,7 +38,6 @@ import cats.effect.{ContextShift, IO, Timer}
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.global
import retry._
import retry.CatsEffect._

// We need an implicit cats.effect.Timer
implicit val timer: Timer[IO] = IO.timer(global)
Expand Down
7 changes: 1 addition & 6 deletions modules/docs/src/main/mdoc/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,7 @@ println(
s"""
|```
|val catsRetryVersion = "${retry.BuildInfo.version.replaceFirst("\\+.*", "")}"
|libraryDependencies ++= Seq(
| "com.github.cb372" %% "cats-retry-core" % catsRetryVersion,
| "com.github.cb372" %% "cats-retry-cats-effect" % catsRetryVersion
|)
|libraryDependencies += "com.github.cb372" %% "cats-retry" % catsRetryVersion,
|```
|""".stripMargin.trim
)
Expand Down Expand Up @@ -98,8 +95,6 @@ import cats.effect.Timer
import scala.concurrent.ExecutionContext.global
implicit val timer: Timer[IO] = IO.timer(global)

// This is so we can use that Timer to perform delays between retries
import retry.CatsEffect._

val flakyRequestWithRetry: IO[String] =
retryingOnAllErrors[String](
Expand Down
Loading

0 comments on commit 91b7d9a

Please sign in to comment.