Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Par functions for Bitraverse #2750

Merged
merged 2 commits into from
Apr 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -81,6 +81,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