Skip to content

Instantly share code, notes, and snippets.

@ianlintner
Last active September 11, 2021 02:28
Show Gist options
  • Save ianlintner/a35e1842288aa7e4b2a811f625574b32 to your computer and use it in GitHub Desktop.
Save ianlintner/a35e1842288aa7e4b2a811f625574b32 to your computer and use it in GitHub Desktop.
Retry a F (functor|future|cats) in Scala e.g. Cats F, EitherT
import cats.data.EitherT
import cats.effect.{Async}
import cats.implicits._
import kittys.{AsyncService}
val someAsyncService = AsyncService()
val key = "imma-key-1234"
case class SomeCaseClass(failure: String)
/**
* Recurse until condition is met could be used for async
* generating uniuqe values, performing deletes retrying flakey web API calls.
*
* This could be generalized and far more idomatic, but it taught me a lot about cats in scala.
*
* This should be a safe way to do sucessive retries the for makes it sequential but it should
* be non-blocking just not parallel as long as your service is non-blocking.
*/
def recursiveRetry(key: String, attempts: Integer): F[Either[String,String]] = {
(for {
// This is getting our F[] that will become our test
existing <- EitherT(checkIfItemExists(key))
// This is the conditional test for flow control
result <- EitherT( existing match {
// Base case / gate around recursion and decrement attempts each time.
case Some(_) if attempts > 0 => recursiveRetry(key, attempts-1)
// Failure Condition these have to be made into F[]'s
case Some(_) => Async[F].pure("Left failure".asLeft[String])
// Return our value
case None => Async[F].pure(keye.asRight[String])
})
} yield result).value
}
/**
* Wrapper around F[either] e.g. from an async service to translate to "right" only either
* In a EitherT based for comprehensions a left ends the computation full stop.
* So to have failure condition everything must be Right so it can be EitherT
*/
def checkIfItemExists(key: String): F[Either[String, Option[String]]] = {
someAsyncService.get(key) map {
case Right(None) => None.asRight[String]
case _ => Some(key).asRight[String]
}
}
// Run the retry recursion
recursiveRetry(key, 5) map {
case Left(fail) => SomeCaseClass(fail).asLeft[String]
case Right(pass) => pass.asRight[SomeCaseClass]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment