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

Improve combinators documentation #264

Merged
merged 2 commits into from
Dec 16, 2020
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
64 changes: 39 additions & 25 deletions modules/docs/src/main/mdoc/docs/combinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ title: Combinators
The library offers a few slightly different ways to wrap your operations with
retries.

## Cheat sheet

| Combinator | Context bound | Handles |
| --- | --- | --- |
| [`retryingM`](#retryingm) | Monad | Failures |
| [`retryingOnSomeErrors`](#retryingonsomeerrors) | MonadError | Errors |
| [`retryingOnAllErrors`](#retryingonallerrors) | MonadError | Errors |
| [`retryingOnFailuresAndSomeErrors`](#retryingonfailuresandsomeerrors) | MonadError | Failures and errors |
| [`retryingOnFailuresAndAllErrors`](#retryingonfailuresandallerrors) | MonadError | Failures and errors |

More information on each combinator is provided below.

## `retryingM`

To use `retryingM`, you pass in a predicate that decides whether you are happy
Expand All @@ -18,10 +30,10 @@ 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:

```scala
def retryingM[M[_]: Monad, A](policy: RetryPolicy[M],
wasSuccessful: A => Boolean,
onFailure: (A, RetryDetails) => M[Unit])
(action: => M[A]): M[A]
def retryingM[M[_]: Monad: Sleep, A](policy: RetryPolicy[M],
wasSuccessful: A => Boolean,
onFailure: (A, RetryDetails) => M[Unit])
(action: => M[A]): M[A]
```

You need to pass in:
Expand Down Expand Up @@ -69,10 +81,11 @@ whether a given error is worth retrying.
The API (modulo some type-inference trickery) looks like this:

```scala
def retryingOnSomeErrors[M[_]: Monad, A, E](policy: RetryPolicy[M],
def retryingOnSomeErrors[M[_]: Sleep, A, E](policy: RetryPolicy[M],
isWorthRetrying: E => Boolean,
onError: (E, RetryDetails) => M[Unit])
(action: => M[A]): M[A]
(action: => M[A])
(implicit ME: MonadError[M, E]): M[A]
```

You need to pass in:
Expand Down Expand Up @@ -113,9 +126,10 @@ retry on all errors.
The API (modulo some type-inference trickery) looks like this:

```scala
def retryingOnAllErrors[M[_]: Monad, A, E](policy: RetryPolicy[M],
def retryingOnAllErrors[M[_]: Sleep, A, E](policy: RetryPolicy[M],
onError: (E, RetryDetails) => M[Unit])
(action: => M[A]): M[A]
(action: => M[A])
(implicit ME: MonadError[M, E]): M[A]
```

You need to pass in:
Expand Down Expand Up @@ -143,22 +157,22 @@ io.unsafeRunSync()

## `retryingOnFailuresAndSomeErrors`

This is a combination of `retryingM` and `retryingOnSomeErrors`. It allows you to specify failure
conditions for both the results and errors that can occur.
This is a combination of `retryingM` and `retryingOnSomeErrors`. It allows you
to specify failure conditions for both the results and errors that can occur.

To use `retryingOnFailuresAndSomeErrors`, you need to pass in predicates that decide
whether a given error or result is worth retrying.
To use `retryingOnFailuresAndSomeErrors`, you need to pass in predicates that
decide whether a given error or result is worth retrying.

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

```scala
def retryingOnFailuresAndSomeErrors[M[_], A, E](policy: RetryPolicy[M],
wasSuccessful: A => Boolean,
isWorthRetrying: E => Boolean,
onFailure: (A, RetryDetails) => M[Unit],
onError: (E, RetryDetails) => M[Unit])
(action: => M[A])
(implicit ME: MonadError[M, E]): M[A]
def retryingOnFailuresAndSomeErrors[M[_]: Sleep, A, E](policy: RetryPolicy[M],
wasSuccessful: A => Boolean,
isWorthRetrying: E => Boolean,
onFailure: (A, RetryDetails) => M[Unit],
onError: (E, RetryDetails) => M[Unit])
(action: => M[A])
(implicit ME: MonadError[M, E]): M[A]
```

You need to pass in:
Expand Down Expand Up @@ -207,12 +221,12 @@ whether a given result is worth retrying.
The API (modulo some type-inference trickery) looks like this:

```scala
def retryingOnFailuresAndAllErrors[M[_], A, E](policy: RetryPolicy[M],
wasSuccessful: A => Boolean,
onFailure: (A, RetryDetails) => M[Unit],
onError: (E, RetryDetails) => M[Unit])
(action: => M[A])
(implicit ME: MonadError[M, E]): M[A]
def retryingOnFailuresAndAllErrors[M[_]: Sleep, A, E](policy: RetryPolicy[M],
wasSuccessful: A => Boolean,
onFailure: (A, RetryDetails) => M[Unit],
onError: (E, RetryDetails) => M[Unit])
(action: => M[A])
(implicit ME: MonadError[M, E]): M[A]
```

You need to pass in:
Expand Down
39 changes: 22 additions & 17 deletions modules/docs/src/main/mdoc/docs/mtl-combinators.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ title: MTL Combinators

# MTL Combinators

The `cats-retry-mtl` module provides two additional retry methods that operating with errors produced
by `Handle` from [cats-mtl](https://github.com/typelevel/cats-mtl).
The `cats-retry-mtl` module provides two additional retry methods that operating
with errors produced by `Handle` from
[cats-mtl](https://github.com/typelevel/cats-mtl).

## Installation

To use `cats-retry-mtl`, add the following dependency to your `build.sbt`:

```scala mdoc:passthrough
println(
s"""
Expand All @@ -24,11 +26,13 @@ println(

## Interaction with MonadError retry

MTL retry works independently from `retry.retryingOnSomeErrors`. The operations `retry.mtl.retryingOnAllErrors` and
`retry.mtl.retryingOnSomeErrors` evaluating retry exclusively on errors produced by `Handle`.
Thus errors produced by `MonadError` are not being taken into account and retry is not triggered.
MTL retry works independently from `retry.retryingOnSomeErrors`. The operations
`retry.mtl.retryingOnAllErrors` and `retry.mtl.retryingOnSomeErrors` evaluating
retry exclusively on errors produced by `Handle`. Thus errors produced by
`MonadError` are not being taken into account and retry is not triggered.

If you want to retry in case of any error, you can chain the methods:

```scala
fa
.retryingOnAllErrors(policy, onError = retry.noop[F, Throwable])
Expand All @@ -40,12 +44,13 @@ fa
This is useful when you are working with an `Handle[M, E]` but you only want
to retry on some errors.

To use `retryingOnSomeErrors`, you need to pass in a predicate that decides whether a given error is worth retrying.
To use `retryingOnSomeErrors`, you need to pass in a predicate that decides
whether a given error is worth retrying.

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

```scala
def retryingOnSomeErrors[M[_]: Monad, A, E: Handle[M, *]](
def retryingOnSomeErrors[M[_]: Monad: Sleep, A, E: Handle[M, *]](
policy: RetryPolicy[M],
isWorthRetrying: E => Boolean,
onError: (E, RetryDetails) => M[Unit]
Expand Down Expand Up @@ -77,14 +82,14 @@ case class AppError(reason: String)
def failingOperation[F[_]: Handle[*[_], AppError]]: F[Unit] =
Handle[F, AppError].raise(AppError("Boom!"))

def isWorthRetrying(error: AppError): Boolean =
def isWorthRetrying(error: AppError): Boolean =
error.reason.contains("Boom!")

def logError[F[_]: Sync](error: AppError, details: RetryDetails): F[Unit] =
Sync[F].delay(println(s"Raised error $error. Details $details"))
def logError[F[_]: Sync](error: AppError, details: RetryDetails): F[Unit] =
Sync[F].delay(println(s"Raised error $error. Details $details"))

val policy = RetryPolicies.limitRetries[Effect](2)

retry.mtl
.retryingOnSomeErrors(policy, isWorthRetrying, logError[Effect])(failingOperation[Effect])
.value
Expand All @@ -99,7 +104,7 @@ retry on all errors.
The API (modulo some type-inference trickery) looks like this:

```scala
def retryingOnSomeErrors[M[_]: Monad, A, E: Handle[M, *]](
def retryingOnSomeErrors[M[_]: Monad: Sleep, A, E: Handle[M, *]](
policy: RetryPolicy[M],
onError: (E, RetryDetails) => M[Unit]
)(action: => M[A]): M[A]
Expand Down Expand Up @@ -129,8 +134,8 @@ case class AppError(reason: String)
def failingOperation[F[_]: Handle[*[_], AppError]]: F[Unit] =
Handle[F, AppError].raise(AppError("Boom!"))

def logError[F[_]: Sync](error: AppError, details: RetryDetails): F[Unit] =
Sync[F].delay(println(s"Raised error $error. Details $details"))
def logError[F[_]: Sync](error: AppError, details: RetryDetails): F[Unit] =
Sync[F].delay(println(s"Raised error $error. Details $details"))

val policy = RetryPolicies.limitRetries[Effect](2)

Expand Down Expand Up @@ -182,11 +187,11 @@ class Service[F[_]: Timer](client: util.FlakyHttpClient)(implicit F: Sync[F], AH
if (string.contains("cool")) F.unit
else AH.raise(AppError("Gif is not cool"))

private def logError(error: Throwable, details: RetryDetails): F[Unit] =
private def logError(error: Throwable, details: RetryDetails): F[Unit] =
F.delay(println(s"Raised error $error. Details $details"))

private def logMtlError(error: AppError, details: RetryDetails): F[Unit] =
F.delay(println(s"Raised MTL error $error. Details $details"))
private def logMtlError(error: AppError, details: RetryDetails): F[Unit] =
F.delay(println(s"Raised MTL error $error. Details $details"))
}

type Effect[A] = EitherT[IO, AppError, A]
Expand Down