Skip to content

Instantly share code, notes, and snippets.

@atamborrino
Last active April 5, 2019 08:40
Show Gist options
  • Save atamborrino/41b7ab61af1385912b0852e4df62017c to your computer and use it in GitHub Desktop.
Save atamborrino/41b7ab61af1385912b0852e4df62017c to your computer and use it in GitHub Desktop.
Monad transformer for Future[A Or Every[Error]]
package models.error
import org.scalactic._
import scala.concurrent.{ExecutionContext, Future}
import org.scalactic.Accumulation._
import scala.util.Success
trait Error
object FutureOr {
type Errors = Every[Error]
object Implicits {
implicit class FutureOps[A](val futAOrError: Future[A Or Errors]) extends AnyVal {
def futureOr: FutureOr[A] = new FutureOr(futAOrError)
}
}
import Implicits._
class FutureOr[+A](private val futAOrError: Future[A Or Errors]) extends AnyVal {
def flatMap[B](f: A => FutureOr[B])(implicit ec: ExecutionContext): FutureOr[B] = {
new FutureOr(
futAOrError.flatMap {
case Good(a) => f(a).future
case Bad(err) => Future.successful(Bad(err))
}
)
}
def map[B](f: A => B)(implicit ec: ExecutionContext): FutureOr[B] =
flatMap(a => new FutureOr(Future.successful(Good(f(a)))))
def mapWithMaybeError[B](f: A => B Or Errors)(implicit ec: ExecutionContext): FutureOr[B] =
flatMap(a => new FutureOr(Future.successful(f(a))))
def zip[B](futBOrError: FutureOr[B])(implicit ec: ExecutionContext): FutureOr[(A, B)] = {
new FutureOr(
(futAOrError zip futBOrError.future).map { case (aOrError, bOrError) => aOrError zip bOrError }
)
}
def onError(f: Errors => Unit)(implicit ec: ExecutionContext): FutureOr[A] = {
futAOrError.onComplete {
case Success(Bad(errors)) => f(errors)
case _ => ()
}
this
}
def onFailure(f: Throwable => Unit)(implicit ec: ExecutionContext): FutureOr[A] = {
futAOrError.onComplete {
case scala.util.Failure(failure) => f(failure)
case _ => ()
}
this
}
def onSuccess(f: A => Unit)(implicit executionContext: ExecutionContext): FutureOr[A] = {
futAOrError.onComplete {
case Success(Good(a)) => f(a)
case _ => ()
}
this
}
def future: Future[A Or Errors] = futAOrError
}
def traverse[A, B](as: Seq[A])(f: A => FutureOr[B])(implicit ec: ExecutionContext): FutureOr[Seq[B]] = {
new FutureOr(Future.traverse(as)(a => f(a).future).map(_.combined))
}
def sequence[A](futAsOrError: Seq[FutureOr[A]])(implicit ec: ExecutionContext): FutureOr[Seq[A]] = {
new FutureOr(Future.sequence(futAsOrError.map(_.future)).map(_.combined))
}
def success[A](a: A): FutureOr[A] = new FutureOr(Future.successful(Good(a)))
def errors[A](errors: Errors): FutureOr[A] = new FutureOr(Future.successful(Bad(errors)))
def error[A](error: Error): FutureOr[A] = new FutureOr(Future.successful(Bad(One(error))))
}
@atamborrino
Copy link
Author

atamborrino commented Mar 31, 2016

Example use:

import org.scalactic._
import models.errors.FutureOr.Implicits._

val futAOrError: Future[A Or Errors]
val futBOrError: Future[B Or Errors]
val futCOrError: Future[C Or Errors]

// doing 3 async tasks sequentially, stopping at the first potential error, stopping at the first potential failure
val res = for {
    a <- futAOrError.futureOr   
    b <- futBOrError(a).futureOr
    c <- futCOrError(b).futureOr
} yield c
res.future: Future[C Or Errors]

// doing 2 async tasks in parallel, accumulating potential errors, stopping at the first failure
val res = futDOrError.futureOr zip futEOrError.futureOr
res.unwrap: Future[(D, E) Or Errors]

// doing 2 async tasks and one task sequentially, stopping at the first potential error, stopping at the first potential failure
val res = for {
    a <- futAOrError.futureOr   
    b <- futBOrError(a).futureOr
    c <- Future.successful(cOrError(b)).futureOr
} yield c
res.future: Future[C Or Errors]

// doing N tasks in parallel, accumulating potential errors, stopping at the first failure
val tasks: Seq[Future[A Or Errors]] = Seq(futA1OrError, futA2OrError, futA3OrError)
val res = FutureOr.sequence(tasks)
res.future: Future[Seq[A] or Errors]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment