Skip to content

Instantly share code, notes, and snippets.

@Mullefa
Created June 21, 2017 08:02
Show Gist options
  • Save Mullefa/d949efd564d7c5c130e0d614c0686cc5 to your computer and use it in GitHub Desktop.
Save Mullefa/d949efd564d7c5c130e0d614c0686cc5 to your computer and use it in GitHub Desktop.
Accumulating errors and results
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