Skip to content

Instantly share code, notes, and snippets.

@atamborrino
Last active August 27, 2019 22:14
Show Gist options
  • Save atamborrino/5a6b7c014b1f7af0a6bd2c3922e5aec6 to your computer and use it in GitHub Desktop.
Save atamborrino/5a6b7c014b1f7af0a6bd2c3922e5aec6 to your computer and use it in GitHub Desktop.
Using Cats vs Scalactic for error validation with error accumulation and async flow
object TestCats {
import cats._
import cats.data._
import cats.instances.future._
import cats.syntax.either._
import cats.syntax.cartesian._
import cats.instances.list._
import cats.syntax.traverse._
trait Error
type Errors = NonEmptyList[Error]
type Result[A] = Either[Errors, A]
// Result
case object AnError extends Error
def aOrError: Result[String] = "a".asRight
def bOrError: Result[Int] = NonEmptyList.of(AnError).asLeft
val res: Result[(String, (String, Int), List[String])] = for {
a <- aOrError
b <- (aOrError.toValidated |@| bOrError.toValidated).tupled.toEither
c <- List(aOrError.toValidated, aOrError.toValidated).sequenceU.toEither
} yield (a, b, c)
// Future Result
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
def futAOrError: Future[Result[String]] = Future.successful("a".asRight)
def futBOrError: Future[Result[Int]] = Future.successful(NonEmptyList.of(AnError).asLeft)
type ResultWithErrorAcc[A] = Validated[Errors, A] // to avoid lambda type
type FutResultWithErrorAcc[A] = Future[Validated[Errors, A]] // to avoid lambda type
implicit val instanceFutureValidatedApplicative = Applicative[Future].compose[ResultWithErrorAcc]
val res2 = for {
a <- EitherT(futAOrError)
b <- EitherT {
Applicative[FutResultWithErrorAcc].product(futAOrError.map(_.toValidated), futBOrError.map(_.toValidated))
.map(_.toEither)
}
c <- EitherT {
List(futAOrError.map(_.toValidated), futAOrError.map(_.toValidated))
.sequence[FutResultWithErrorAcc, String] // does not compile without explicit type
.map(_.toEither)
}
} yield (a, b, c)
val res3: Future[Result[(String, (String, Int), List[String])]] = res2.value
}
object TestScalactic {
import org.scalactic.Accumulation._
import org.scalactic._
trait Error
type Errors = Every[Error]
type Result[A] = A Or Errors
// Result
case object AnError extends Error
def aOrError: Result[String] = Good("a")
def bOrError: Result[Int] = Bad(One(AnError))
val res: Result[(String, (String, Int), Seq[String])] = for {
a <- aOrError
b <- aOrError zip bOrError
c <- Seq(aOrError, aOrError).combined
} yield (a, b, c)
// Future Result
import scala.concurrent._
import scala.concurrent.ExecutionContext.Implicits.global
import FutureOr.Implicits._
def futAOrError: Future[Result[String]] = Future.successful(Good("a"))
def futBOrError: Future[Result[Int]] = Future.successful(Bad(One(AnError)))
val res2 = for {
a <- futAOrError.toFutureOr
b <- futAOrError.toFutureOr zip futBOrError.toFutureOr
c <- FutureOr.sequence(Seq(futAOrError.toFutureOr, futAOrError.toFutureOr))
} yield (a, b, c)
val res3: Future[Result[(String, (String, Int), Seq[String])]] = res2.toFuture
/**
* Annex: minimal FutureOr monad transformer for Scalactic
*/
class FutureOr[+A](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).toFuture
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 zip[B](futBOrError: FutureOr[B])(implicit ec: ExecutionContext): FutureOr[(A, B)] = {
new FutureOr(
(futAOrError zip futBOrError.toFuture).map { case (aOrError, bOrError) => aOrError zip bOrError }
)
}
def toFuture: Future[A Or Errors] = futAOrError
}
object FutureOr {
object Implicits {
implicit class FutureOps[A](val futAOrError: Future[A Or Errors]) extends AnyVal {
def toFutureOr: FutureOr[A] = new FutureOr(futAOrError)
}
}
def sequence[A](futAsOrError: Seq[FutureOr[A]])(implicit ec: ExecutionContext): FutureOr[Seq[A]] = {
new FutureOr(Future.sequence(futAsOrError.map(_.toFuture)).map(_.combined))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment