Created
June 21, 2017 08:02
-
-
Save Mullefa/d949efd564d7c5c130e0d614c0686cc5 to your computer and use it in GitHub Desktop.
Accumulating errors and results
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
import cats.data.Ior | |
import cats.kernel.Monoid | |
import cats.kernel.Semigroup | |
import simulacrum.typeclass | |
import scala.concurrent.ExecutionContext | |
import scala.concurrent.Future | |
object GoOver extends App { | |
@typeclass trait Map2Option[F[_]] { | |
def pure[A](x: A): F[A] | |
def map2Option[A, B, Z](fa: F[A], fb: F[B])(f: (Option[A], Option[B]) => Option[Z]): F[Z] | |
} | |
@typeclass trait GoOver[F[_]] { | |
def goOver[A, B, G[_]](fa: F[A])(f: A => G[B])(implicit M: Map2Option[G]): G[F[B]] | |
} | |
// Reduces options containing empty elements to None before combining | |
implicit def semigroupOption[A : Monoid]: Semigroup[Option[A]] = new Semigroup[Option[A]] { | |
override def combine(x: Option[A], y: Option[A]): Option[A] = | |
(x.filterNot(_ == Monoid[A].empty), y.filterNot(_ == Monoid[A].empty)) match { | |
case (Some(a), Some(b)) => Some(Semigroup[A].combine(a, b)) | |
case (xne @ Some(_), _) => xne | |
case (_, yne @ Some(_)) => yne | |
case _ => None | |
} | |
} | |
implicit def map2OptionIor[E : Monoid]: Map2Option[Ior[E, ?]] = new Map2Option[Ior[E, ?]] { | |
private[this] val SG = Semigroup[Option[E]] | |
override def pure[A](x: A): Ior[E, A] = Ior.right(x) | |
override def map2Option[A, B, Z](fa: Ior[E, A], fb: Ior[E, B])(f: (Option[A], Option[B]) => Option[Z]): Ior[E, Z] = | |
((SG.combine(fa.left, fb.left), f(fa.right, fb.right)): @unchecked) match { | |
case (Some(e), Some(z)) => Ior.both(e, z) | |
case (Some(e), _) => Ior.left(e) | |
case (_, Some(z)) => Ior.right(z) | |
} | |
} | |
type FutureIor[E, A] = Future[Ior[E, A]] | |
implicit def map2OptionFutureIor[E : Monoid](implicit ec: ExecutionContext): Map2Option[FutureIor[E, ?]] = | |
new Map2Option[FutureIor[E, ?]] { | |
override def pure[A](x: A): FutureIor[E, A] = Future.successful(Ior.right(x)) | |
override def map2Option[A, B, Z](fa: FutureIor[E, A], fb: FutureIor[E, B])(f: (Option[A], Option[B]) => Option[Z]): FutureIor[E, Z] = | |
fa.zip(fb).map { case (iora, iorb) => Map2Option[Ior[E, ?]].map2Option(iora, iorb)(f) } | |
} | |
implicit def monoidList[A]: Monoid[List[A]] = new Monoid[List[A]] { | |
override def empty: List[A] = List.empty | |
override def combine(x: List[A], y: List[A]): List[A] = x ++ y | |
} | |
implicit object goOverList extends GoOver[List] { | |
override def goOver[A, B, G[_]](fa: List[A])(f: (A) => G[B])(implicit G: Map2Option[G]): G[List[B]] = | |
fa.foldRight(G.pure(List.empty[B])) { case (a, glb) => | |
G.map2Option(f(a), glb) { case (ob, olb) => Semigroup[Option[List[B]]].combine(ob.map(List(_)), olb) } | |
} | |
} | |
case class ParseException(input: String) | |
type FutureParseResult[A] = Future[Ior[List[ParseException], A]] | |
def parseAsync(x: String)(implicit ec: ExecutionContext): FutureParseResult[Int] = | |
Future { | |
try { | |
Ior.right(x.toDouble.toInt) | |
} catch { | |
case _: Exception => Ior.left(List(ParseException(x))) | |
} | |
} | |
import GoOver.ops._ | |
import scala.concurrent.ExecutionContext.Implicits.global | |
import scala.concurrent.duration._ | |
def inspectResult(input: List[String]): Unit = | |
println(scala.concurrent.Await.result(input.goOver(parseAsync), 10.second)) | |
// Right(List()) | |
inspectResult(List()) | |
// Left(List("hello", "world")) | |
inspectResult(List("hello", "world")) | |
// Both(List("hello"), List(1)) | |
inspectResult(List("hello", "1")) | |
// Right(List(1, 2,)) | |
inspectResult(List("1", "2")) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment