Skip to content

Commit

Permalink
Add parTuple (#2183)
Browse files Browse the repository at this point in the history
* Added parTupled boilerplate

* Refactored names

* Changed tupled to tuple

* Removed duplication for nested expansion

* Renamed syntax to parTupled

* Changed ParallelArityFunctions2 to abstract class

* Added some tests for parTupled

* Added more tests
  • Loading branch information
barambani authored and kailuowang committed Mar 12, 2018
1 parent 904373e commit 2ee184e
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 5 deletions.
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/Parallel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ object NonEmptyParallel {
def apply[M[_], F[_]](implicit P: NonEmptyParallel[M, F]): NonEmptyParallel[M, F] = P
}

object Parallel extends ParallelArityFunctions {
object Parallel extends ParallelArityFunctions2 {

def apply[M[_], F[_]](implicit P: Parallel[M, F]): Parallel[M, F] = P

Expand Down
47 changes: 43 additions & 4 deletions project/Boilerplate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ object Boilerplate {
GenApplyArityFunctions,
GenTupleSemigroupalSyntax,
GenParallelArityFunctions,
GenParallelArityFunctions2,
GenTupleParallelSyntax
)

Expand Down Expand Up @@ -213,6 +214,11 @@ object Boilerplate {
}
}

final case class ParallelNestedExpansions(arity: Int) {
val products = (0 until (arity - 2)).foldRight(s"Parallel.parProduct(m${arity - 2}, m${arity - 1})")((i, acc) => s"Parallel.parProduct(m$i, $acc)")
val `(a..n)` = (0 until (arity - 2)).foldRight(s"(a${arity - 2}, a${arity - 1})")((i, acc) => s"(a$i, $acc)")
}

object GenParallelArityFunctions extends Template {
def filename(root: File) = root / "cats" / "ParallelArityFunctions.scala"
override def range = 2 to maxArity
Expand All @@ -223,9 +229,7 @@ object Boilerplate {
val fargs = (0 until arity) map { "m" + _ }
val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", "
val fargsS = fargs mkString ", "

val nestedProducts = (0 until (arity - 2)).foldRight(s"Parallel.parProduct(m${arity - 2}, m${arity - 1})")((i, acc) => s"Parallel.parProduct(m$i, $acc)")
val `nested (a..n)` = (0 until (arity - 2)).foldRight(s"(a${arity - 2}, a${arity - 1})")((i, acc) => s"(a$i, $acc)")
val nestedExpansion = ParallelNestedExpansions(arity)

block"""
|package cats
Expand All @@ -240,7 +244,38 @@ object Boilerplate {
|trait ParallelArityFunctions {
- /** @group ParMapArity */
- def parMap$arity[M[_], F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] =
- p.flatMap.map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) }
- p.flatMap.map(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) }
|}
"""
}
}

object GenParallelArityFunctions2 extends Template {
def filename(root: File) = root / "cats" / "ParallelArityFunctions2.scala"
override def range = 2 to maxArity
def content(tv: TemplateVals) = {
import tv._

val tpes = synTypes map { tpe => s"M[$tpe]" }
val fargs = (0 until arity) map { "m" + _ }
val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", "
val fargsS = fargs mkString ", "
val nestedExpansion = ParallelNestedExpansions(arity)

block"""
|package cats
|
|/**
| * @groupprio Ungrouped 0
| *
| * @groupname ParTupleArity parTuple arity
| * @groupdesc ParTupleArity Higher-arity parTuple methods
| * @groupprio ParTupleArity 999
| */
|abstract class ParallelArityFunctions2 extends ParallelArityFunctions {
- /** @group ParTupleArity */
- def parTuple$arity[M[_], F[_], ${`A..N`}]($fparams)(implicit p: NonEmptyParallel[M, F]): M[(${`A..N`})] =
- p.flatMap.map(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => (${`a..n`}) }
|}
"""
}
Expand Down Expand Up @@ -326,6 +361,9 @@ object Boilerplate {
if (arity == 1) s"def parMap[F[_], Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = p.flatMap.map($tupleArgs)(f)"
else s"def parMapN[F[_], Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M, F]): M[Z] = Parallel.parMap$arity($tupleArgs)(f)"

val parTupled =
if (arity == 1) ""
else s"def parTupled[F[_]](implicit p: NonEmptyParallel[M, F]): M[(${`A..N`})] = Parallel.parTuple$arity($tupleArgs)"

block"""
|package cats
Expand All @@ -339,6 +377,7 @@ object Boilerplate {
|
-private[syntax] final class Tuple${arity}ParallelOps[M[_], ${`A..N`}]($tupleTpe) {
- $parMap
- $parTupled
-}
|
"""
Expand Down
48 changes: 48 additions & 0 deletions tests/src/test/scala/cats/tests/ParallelSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,54 @@ class ParallelSuite extends CatsSuite with ApplicativeErrorForEitherTest {
}
}

test("ParTupled of NonEmptyList should be consistent with ParMap of Tuple.apply") {
forAll { (fa: NonEmptyList[Int], fb: NonEmptyList[Int], fc: NonEmptyList[Int], fd: NonEmptyList[Int]) =>
(fa, fb, fc, fd).parTupled should ===((fa, fb, fc, fd).parMapN(Tuple4.apply))
}
}

test("ParTupled of NonEmptyVector should be consistent with ParMap of Tuple.apply") {
forAll { (fa: NonEmptyVector[Int], fb: NonEmptyVector[Int], fc: NonEmptyVector[Int], fd: NonEmptyVector[Int]) =>
(fa, fb, fc, fd).parTupled should ===((fa, fb, fc, fd).parMapN(Tuple4.apply))
}
}

test("ParTupled of List should be consistent with ParMap of Tuple.apply") {
forAll { (fa: List[Int], fb: List[Int], fc: List[Int], fd: List[Int]) =>
(fa, fb, fc, fd).parTupled should ===((fa, fb, fc, fd).parMapN(Tuple4.apply))
}
}

test("ParTupled of Vector should be consistent with ParMap of Tuple.apply") {
forAll { (fa: Vector[Int], fb: Vector[Int], fc: Vector[Int], fd: Vector[Int]) =>
(fa, fb, fc, fd).parTupled should ===((fa, fb, fc, fd).parMapN(Tuple4.apply))
}
}

test("ParTupled of Stream should be consistent with ParMap of Tuple.apply") {
forAll { (fa: Stream[Int], fb: Stream[Int], fc: Stream[Int], fd: Stream[Int]) =>
(fa, fb, fc, fd).parTupled should === ((fa, fb, fc, fd).parMapN(Tuple4.apply))
}
}

test("ParTupled of List should be consistent with zip") {
forAll { (fa: List[Int], fb: List[Int], fc: List[Int], fd: List[Int]) =>
(fa, fb, fc, fd).parTupled should === (fa.zip(fb).zip(fc).zip(fd).map { case (((a, b), c), d) => (a, b, c, d) })
}
}

test("ParTupled of Vector should be consistent with zip") {
forAll { (fa: Vector[Int], fb: Vector[Int], fc: Vector[Int], fd: Vector[Int]) =>
(fa, fb, fc, fd).parTupled should === (fa.zip(fb).zip(fc).zip(fd).map { case (((a, b), c), d) => (a, b, c, d) })
}
}

test("ParTupled of Stream should be consistent with zip") {
forAll { (fa: Stream[Int], fb: Stream[Int], fc: Stream[Int], fd: Stream[Int]) =>
(fa, fb, fc, fd).parTupled should === (fa.zip(fb).zip(fc).zip(fd).map { case (((a, b), c), d) => (a, b, c, d) })
}
}

test("IorT leverages parallel effect instances when it exists") {
case class Marker(value: String) extends Exception("marker") {
override def fillInStackTrace: Throwable = null
Expand Down

0 comments on commit 2ee184e

Please sign in to comment.