Skip to content

Commit

Permalink
Par functions for Bitraverse (#2750)
Browse files Browse the repository at this point in the history
* add par functions for Bitraverse

* add tests for parBisequence
  • Loading branch information
catostrophe authored and kailuowang committed Apr 8, 2019
1 parent c9f10f2 commit f828924
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 2 deletions.
46 changes: 46 additions & 0 deletions core/src/main/scala/cats/Parallel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,52 @@ object Parallel extends ParallelArityFunctions2 {
P.sequential(gtb)
}

/**
* Like `Bitraverse[A].bitraverse`, but uses the applicative instance
* corresponding to the Parallel instance instead.
*/
def parBitraverse[T[_, _]: Bitraverse, M[_], F[_], A, B, C, D](
tab: T[A, B]
)(f: A => M[C], g: B => M[D])(implicit P: Parallel[M, F]): M[T[C, D]] = {
val ftcd: F[T[C, D]] =
Bitraverse[T].bitraverse(tab)(f.andThen(P.parallel.apply), g.andThen(P.parallel.apply))(P.applicative)
P.sequential(ftcd)
}

/**
* Like `Bitraverse[A].bisequence`, but uses the applicative instance
* corresponding to the Parallel instance instead.
*/
def parBisequence[T[_, _]: Bitraverse, M[_], F[_], A, B](
tmamb: T[M[A], M[B]]
)(implicit P: Parallel[M, F]): M[T[A, B]] = {
val ftab: F[T[A, B]] = Bitraverse[T].bitraverse(tmamb)(P.parallel.apply, P.parallel.apply)(P.applicative)
P.sequential(ftab)
}

/**
* Like `Bitraverse[A].leftTraverse`, but uses the applicative instance
* corresponding to the Parallel instance instead.
*/
def parLeftTraverse[T[_, _]: Bitraverse, M[_], F[_], A, B, C](
tab: T[A, B]
)(f: A => M[C])(implicit P: Parallel[M, F]): M[T[C, B]] = {
val ftcb: F[T[C, B]] =
Bitraverse[T].bitraverse(tab)(f.andThen(P.parallel.apply), P.applicative.pure)(P.applicative)
P.sequential(ftcb)
}

/**
* Like `Bitraverse[A].leftSequence`, but uses the applicative instance
* corresponding to the Parallel instance instead.
*/
def parLeftSequence[T[_, _]: Bitraverse, M[_], F[_], A, B](
tmab: T[M[A], B]
)(implicit P: Parallel[M, F]): M[T[A, B]] = {
val ftab: F[T[A, B]] = Bitraverse[T].bitraverse(tmab)(P.parallel.apply, P.applicative.pure)(P.applicative)
P.sequential(ftab)
}

/**
* Like `Applicative[F].ap`, but uses the applicative instance
* corresponding to the Parallel instance instead.
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ abstract class AllSyntaxBinCompat
with AllSyntaxBinCompat2
with AllSyntaxBinCompat3
with AllSyntaxBinCompat4
with AllSyntaxBinCompat5

trait AllSyntax
extends AlternativeSyntax
Expand Down Expand Up @@ -87,3 +88,5 @@ trait AllSyntaxBinCompat4
with ReducibleSyntaxBinCompat0
with FoldableSyntaxBinCompat1
with BitraverseSyntaxBinCompat0

trait AllSyntaxBinCompat5 extends ParallelBitraverseSyntax
7 changes: 6 additions & 1 deletion core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ package object syntax {
object nested extends NestedSyntax
object option extends OptionSyntax
object order extends OrderSyntax
object parallel extends ParallelSyntax with ParallelTraverseSyntax with ParallelFlatSyntax with ParallelApplySyntax
object parallel
extends ParallelSyntax
with ParallelTraverseSyntax
with ParallelFlatSyntax
with ParallelApplySyntax
with ParallelBitraverseSyntax
object partialOrder extends PartialOrderSyntax
object profunctor extends ProfunctorSyntax
object reducible extends ReducibleSyntax with ReducibleSyntaxBinCompat0
Expand Down
45 changes: 44 additions & 1 deletion core/src/main/scala/cats/syntax/parallel.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.syntax

import cats.{FlatMap, Foldable, Monad, Parallel, Traverse}
import cats.{Bitraverse, FlatMap, Foldable, Monad, Parallel, Traverse}

trait ParallelSyntax extends TupleParallelSyntax {

Expand Down Expand Up @@ -39,6 +39,28 @@ trait ParallelTraverseSyntax {
new ParallelSequence_Ops[T, M, A](tma)
}

trait ParallelBitraverseSyntax {
implicit final def catsSyntaxParallelBitraverse[T[_, _]: Bitraverse, A, B](
tab: T[A, B]
): ParallelBitraverseOps[T, A, B] =
new ParallelBitraverseOps[T, A, B](tab)

implicit final def catsSyntaxParallelBisequence[T[_, _]: Bitraverse, M[_], A, B](
tmamb: T[M[A], M[B]]
): ParallelBisequenceOps[T, M, A, B] =
new ParallelBisequenceOps[T, M, A, B](tmamb)

implicit final def catsSyntaxParallelLeftTraverse[T[_, _]: Bitraverse, A, B](
tab: T[A, B]
): ParallelLeftTraverseOps[T, A, B] =
new ParallelLeftTraverseOps[T, A, B](tab)

implicit final def catsSyntaxParallelLeftSequence[T[_, _]: Bitraverse, M[_], A, B](
tmab: T[M[A], B]
): ParallelLeftSequenceOps[T, M, A, B] =
new ParallelLeftSequenceOps[T, M, A, B](tmab)
}

final class ParallelTraversableOps[T[_], A](private val ta: T[A]) extends AnyVal {
def parTraverse[M[_]: Monad, F[_], B](f: A => M[B])(implicit T: Traverse[T], P: Parallel[M, F]): M[T[B]] =
Parallel.parTraverse(ta)(f)
Expand Down Expand Up @@ -86,3 +108,24 @@ final class ParallelApplyOps[M[_], A, B](private val mab: M[A => B]) extends Any
def <&>[F[_]](ma: M[A])(implicit P: Parallel[M, F]): M[B] =
Parallel.parAp(mab)(ma)
}

final class ParallelBitraverseOps[T[_, _], A, B](private val tab: T[A, B]) extends AnyVal {
def parBitraverse[M[_], F[_], C, D](f: A => M[C], g: B => M[D])(implicit T: Bitraverse[T],
P: Parallel[M, F]): M[T[C, D]] =
Parallel.parBitraverse(tab)(f, g)
}

final class ParallelBisequenceOps[T[_, _], M[_], A, B](private val tmamb: T[M[A], M[B]]) extends AnyVal {
def parBisequence[F[_]](implicit T: Bitraverse[T], P: Parallel[M, F]): M[T[A, B]] =
Parallel.parBisequence(tmamb)
}

final class ParallelLeftTraverseOps[T[_, _], A, B](private val tab: T[A, B]) extends AnyVal {
def parLeftTraverse[M[_], F[_], C](f: A => M[C])(implicit T: Bitraverse[T], P: Parallel[M, F]): M[T[C, B]] =
Parallel.parLeftTraverse(tab)(f)
}

final class ParallelLeftSequenceOps[T[_, _], M[_], A, B](private val tmab: T[M[A], B]) extends AnyVal {
def parLeftSequence[F[_]](implicit T: Bitraverse[T], P: Parallel[M, F]): M[T[A, B]] =
Parallel.parLeftSequence(tmab)
}
1 change: 1 addition & 0 deletions testkit/src/main/scala/cats/tests/CatsSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ trait CatsSuite
with AllSyntaxBinCompat2
with AllSyntaxBinCompat3
with AllSyntaxBinCompat4
with AllSyntaxBinCompat5
with StrictCatsEquality { self: FunSuiteLike =>

implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
Expand Down
107 changes: 107 additions & 0 deletions tests/src/test/scala/cats/tests/ParallelSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,113 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest {
}
}

type ListTuple2[A, B] = List[(A, B)]
implicit val catsBitraverseForListTuple2 = new Bitraverse[ListTuple2] {
def bifoldLeft[A, B, C](fab: ListTuple2[A, B], c: C)(f: (C, A) => C, g: (C, B) => C): C =
fab.foldLeft(c) { case (c, (a, b)) => g(f(c, a), b) }
def bifoldRight[A, B, C](fab: ListTuple2[A, B], lc: Eval[C])(f: (A, Eval[C]) => Eval[C],
g: (B, Eval[C]) => Eval[C]): Eval[C] = {
def loop(abs: ListTuple2[A, B]): Eval[C] =
abs match {
case Nil => lc
case (a, b) :: t => f(a, g(b, Eval.defer(loop(t))))
}
Eval.defer(loop(fab))
}
def bitraverse[G[_], A, B, C, D](
fab: ListTuple2[A, B]
)(f: A => G[C], g: B => G[D])(implicit G: Applicative[G]): G[ListTuple2[C, D]] = {
def loop(abs: ListTuple2[A, B]): Eval[G[ListTuple2[C, D]]] =
abs match {
case Nil => Now(G.pure(List.empty))
case (a, b) :: t => G.map2Eval(G.product(f(a), g(b)), Eval.defer(loop(t)))(_ :: _)
}
loop(fab).value
}
}

test("ParBisequence Either should accumulate errors") {
forAll { es: ListTuple2[Either[String, Int], Either[String, Int]] =>
val lefts = es
.flatMap {
case (a, b) => List(a, b)
}
.collect {
case Left(e) => e
}
.foldMap(identity)

es.parBisequence.fold(identity, i => Monoid[String].empty) should ===(lefts)
}
}

test("ParBisequence Ior should accumulate errors") {
forAll { es: ListTuple2[Ior[String, Int], Ior[String, Int]] =>
val lefts = es
.flatMap {
case (a, b) => List(a, b)
}
.map(_.left)
.collect {
case Some(e) => e
}
.foldMap(identity)

es.parBisequence.left.getOrElse(Monoid[String].empty) should ===(lefts)
}
}

test("ParBisequence Ior should bisequence values") {
forAll { es: ListTuple2[Ior[String, Int], Ior[String, Int]] =>
es.parBisequence.right should ===(es.bimap(_.toOption, _.toOption).bisequence)
}
}

test("ParBitraverse identity should be equivalent to parBisequence") {
forAll { es: (Either[String, Int], Either[String, Long]) =>
es.parBitraverse(identity, identity) should ===(es.parBisequence)
}
}

test("ParLeftSequence Either should accumulate errors") {
forAll { es: ListTuple2[Either[String, Int], Int] =>
val lefts = es
.collect {
case (Left(e), _) => e
}
.foldMap(identity)

es.parLeftSequence.fold(identity, i => Monoid[String].empty) should ===(lefts)
}
}

test("ParLeftSequence Ior should accumulate errors") {
forAll { es: ListTuple2[Ior[String, Int], Int] =>
val lefts = es
.map {
case (a, b) => a.left
}
.collect {
case Some(e) => e
}
.foldMap(identity)

es.parLeftSequence.left.getOrElse(Monoid[String].empty) should ===(lefts)
}
}

test("ParLeftSequence Ior should leftSequence values") {
forAll { es: ListTuple2[Ior[String, Int], Int] =>
es.parLeftSequence.right should ===(es.bimap(_.toOption, identity).leftSequence)
}
}

test("ParLeftTraverse identity should be equivalent to parLeftSequence") {
forAll { es: (Either[String, Int], Either[String, Long]) =>
es.parLeftTraverse(identity) should ===(es.parLeftSequence)
}
}

test("ParFlatTraverse should be equivalent to parTraverse map flatten") {
forAll { es: List[Either[String, Int]] =>
val f: Int => List[Int] = i => List(i, i + 1)
Expand Down
14 changes: 14 additions & 0 deletions tests/src/test/scala/cats/tests/SyntaxSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,20 @@ object SyntaxSuite
(fa, fb, fc).parMapN(f)
}

def testParallelBi[M[_], F[_], T[_, _]: Bitraverse, A, B, C, D](implicit P: Parallel[M, F]): Unit = {
val tab = mock[T[A, B]]
val f = mock[A => M[C]]
val g = mock[B => M[D]]
val mtcd = tab.parBitraverse(f, g)
val mtcb = tab.parLeftTraverse(f)

val tmamb = mock[T[M[A], M[B]]]
val mtab1 = tmamb.parBisequence

val tmab = mock[T[M[A], B]]
val mtab2 = tmab.parLeftSequence
}

def testReducible[F[_]: Reducible, G[_]: Apply: SemigroupK, A: Semigroup, B, Z]: Unit = {
val fa = mock[F[A]]
val f1 = mock[(A, A) => A]
Expand Down

0 comments on commit f828924

Please sign in to comment.