Last active
August 27, 2019 22:14
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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