From a0e0efb6bafdf3557647046d9f7a8d0e817eddd3 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Fri, 17 Jun 2022 18:47:49 +0200 Subject: [PATCH 1/9] Added flatParMapN --- project/Boilerplate.scala | 10 ++++++++++ .../shared/src/test/scala/cats/tests/SyntaxSuite.scala | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 06348ce071..1caa88282e 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -349,6 +349,9 @@ object Boilerplate { - /** @group ParMapArity */ - def parMap$arity[M[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M]): M[Z] = - p.flatMap.map(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) } + - + - def flatParMap$arity[M[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = + - p.flatMap.flatten(p.flatMap.map(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) }) |} """ } @@ -472,6 +475,12 @@ object Boilerplate { else s"def parMapN[Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M]): M[Z] = Parallel.parMap$arity($tupleArgs)(f)" + val flatParMap = + if (arity == 1) + s"def flatParMap[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatMap($tupleArgs)(f)" + else + s"def flatParMapN[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatten(Parallel.parMap$arity($tupleArgs)(f))" + val parTupled = if (arity == 1) "" else @@ -490,6 +499,7 @@ object Boilerplate { -private[syntax] final class Tuple${arity}ParallelOps[M[_], ${`A..N`}](private val $tupleTpe) extends Serializable { - $parMap - $parTupled + - $flatParMap -} | """ diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala index 432e4f5596..7a9cdf60c1 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala @@ -246,9 +246,13 @@ object SyntaxSuite { val fb = mock[M[B]] val fc = mock[M[C]] val f = mock[(A, B, C) => Z] + val mf = mock[(A, B, C) => M[Z]] tfabc.parMapN(f) (fa, fb, fc).parMapN(f) + + tfabc.flatParMapN(mf) + (fa, fb, fc).flatParMapN(mf) } def testParallelBi[M[_], F[_], T[_, _]: Bitraverse, A, B, C, D](implicit P: Parallel.Aux[M, F]): Unit = { From fe96ffaad1f287a88712e5bee95c58d0a4a68720 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sat, 18 Jun 2022 08:25:11 +0200 Subject: [PATCH 2/9] Added flatParMapN tests for 1-arity --- tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala index 7a9cdf60c1..68235071ac 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala @@ -253,6 +253,11 @@ object SyntaxSuite { tfabc.flatParMapN(mf) (fa, fb, fc).flatParMapN(mf) + + val tfa = mock[Tuple1[M[A]]] + val mfone = mock[A => M[Z]] + + tfa.flatParMap(mfone) } def testParallelBi[M[_], F[_], T[_, _]: Bitraverse, A, B, C, D](implicit P: Parallel.Aux[M, F]): Unit = { From f62d98b181caeea721062ea2e0b12355d4644631 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Tue, 21 Jun 2022 16:50:03 +0200 Subject: [PATCH 3/9] Refactored .map.flatten in to .flatmap --- project/Boilerplate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 1caa88282e..aa8379f696 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -351,7 +351,7 @@ object Boilerplate { - p.flatMap.map(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) } - - def flatParMap$arity[M[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = - - p.flatMap.flatten(p.flatMap.map(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) }) + - p.flatMap.flatMap(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) } |} """ } From 997e803f8689bdab23edf5790c73095f0097f124 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Sat, 17 Sep 2022 21:39:15 +0200 Subject: [PATCH 4/9] Modified function name as per discussion --- project/Boilerplate.scala | 10 +++++----- .../shared/src/test/scala/cats/tests/SyntaxSuite.scala | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index aa8379f696..111f6f542d 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -350,7 +350,7 @@ object Boilerplate { - def parMap$arity[M[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M]): M[Z] = - p.flatMap.map(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) } - - - def flatParMap$arity[M[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = + - def parFlatMap$arity[M[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = - p.flatMap.flatMap(${nestedExpansion.products}) { case ${nestedExpansion.`(a..n)`} => f(${`a..n`}) } |} """ @@ -475,11 +475,11 @@ object Boilerplate { else s"def parMapN[Z](f: (${`A..N`}) => Z)(implicit p: NonEmptyParallel[M]): M[Z] = Parallel.parMap$arity($tupleArgs)(f)" - val flatParMap = + val parFlatMap = if (arity == 1) - s"def flatParMap[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatMap($tupleArgs)(f)" + s"def parFlatMap[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatMap($tupleArgs)(f)" else - s"def flatParMapN[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatten(Parallel.parMap$arity($tupleArgs)(f))" + s"def parFlatMapN[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatten(Parallel.parMap$arity($tupleArgs)(f))" val parTupled = if (arity == 1) "" @@ -499,7 +499,7 @@ object Boilerplate { -private[syntax] final class Tuple${arity}ParallelOps[M[_], ${`A..N`}](private val $tupleTpe) extends Serializable { - $parMap - $parTupled - - $flatParMap + - $parFlatMap -} | """ diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala index 68235071ac..451d627a67 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala @@ -251,13 +251,13 @@ object SyntaxSuite { tfabc.parMapN(f) (fa, fb, fc).parMapN(f) - tfabc.flatParMapN(mf) - (fa, fb, fc).flatParMapN(mf) + tfabc.parFlatMapN(mf) + (fa, fb, fc).parFlatMapN(mf) val tfa = mock[Tuple1[M[A]]] val mfone = mock[A => M[Z]] - tfa.flatParMap(mfone) + tfa.parFlatMap(mfone) } def testParallelBi[M[_], F[_], T[_, _]: Bitraverse, A, B, C, D](implicit P: Parallel.Aux[M, F]): Unit = { From 06ac73b6834cda88f22d2cb6cf17f3a186101149 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Wed, 21 Sep 2022 00:30:30 +0200 Subject: [PATCH 5/9] Added ParallelSuite tests for parFlatMapN --- .../test/scala/cats/tests/ParallelSuite.scala | 32 +++++++++++++++++++ .../test/scala/cats/tests/SyntaxSuite.scala | 25 ++++++++++----- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala index 68929f15e3..cb97fed9ae 100644 --- a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala @@ -390,6 +390,38 @@ class ParallelSuite } } + test("ParFlatMapN over List should be consistent with parMapN flatten") { + forAll { (as: List[Int], bs: List[Int], cs: List[Int]) => + val mf: (Int, Int, Int) => List[Int] = _ :: _ :: _ :: Nil + assert((as, bs, cs).parFlatMapN(mf) == (as, bs, cs).parMapN(mf).flatten) + } + } + + test("ParFlatMapN over NonEmptyList should be consistent with parMapN flatten") { + forAll { (as: NonEmptyList[Int], bs: NonEmptyList[Int], cs: NonEmptyList[Int]) => + val mf: (Int, Int, Int) => NonEmptyList[Int] = (a, b, c) => NonEmptyList.of(a, b, c) + assert((as, bs, cs).parFlatMapN(mf) == (as, bs, cs).parMapN(mf).flatten) + } + } + + test("ParFlatMap over List should be consistent with flatmap") { + forAll { as: List[Int] => assert(Tuple1(as).parFlatMap(_ :: Nil) == Tuple1(as).flatMap(_ :: Nil)) } + } + + test("ParFlatMap over NonEmptyList should be consistent with flatmap") { + forAll { as: NonEmptyList[Int] => + assert(Tuple1(as).parFlatMap(NonEmptyList.one) == Tuple1(as).flatMap(NonEmptyList.one)) + } + } + + test("ParMapN over f should be consistent with parFlatMapN over f lifted in Kleisli") { + forAll { (as: List[Int], bs: List[Int]) => + val f: (Int, Int) => Int = _ + _ + val mf: (Int, Int) => List[Int] = Function.untupled(Kleisli.fromFunction[List, (Int, Int)](f.tupled).run) + assert((as, bs).parMapN(f) == (as, bs).parFlatMapN(mf)) + } + } + 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]) => assert((fa, fb, fc, fd).parTupled === ((fa, fb, fc, fd).parMapN(Tuple4.apply))) diff --git a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala index 451d627a67..50592e7f3f 100644 --- a/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/SyntaxSuite.scala @@ -241,18 +241,27 @@ object SyntaxSuite { } def testParallelTuple[M[_]: Monad, F[_], A, B, C, Z](implicit P: NonEmptyParallel.Aux[M, F]) = { - val tfabc = mock[(M[A], M[B], M[C])] val fa = mock[M[A]] val fb = mock[M[B]] val fc = mock[M[C]] - val f = mock[(A, B, C) => Z] - val mf = mock[(A, B, C) => M[Z]] - tfabc.parMapN(f) - (fa, fb, fc).parMapN(f) - - tfabc.parFlatMapN(mf) - (fa, fb, fc).parFlatMapN(mf) + val tfabc = mock[(M[A], M[B], M[C])] + val fthree = mock[(A, B, C) => Z] + val mfthree = mock[(A, B, C) => M[Z]] + + tfabc.parMapN(fthree) + (fa, fb, fc).parMapN(fthree) + tfabc.parFlatMapN(mfthree) + (fa, fb, fc).parFlatMapN(mfthree) + + val tfab = mock[(M[A], M[B])] + val ftwo = mock[(A, B) => Z] + val mftwo = mock[(A, B) => M[Z]] + + tfab.parMapN(ftwo) + (fa, fb).parMapN(ftwo) + tfab.parFlatMapN(mftwo) + (fa, fb).parFlatMapN(mftwo) val tfa = mock[Tuple1[M[A]]] val mfone = mock[A => M[Z]] From 5e41658d9d161ba41155863a3eaa9bc02dc44bb7 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Tue, 27 Sep 2022 22:49:23 +0200 Subject: [PATCH 6/9] Changed implementation using arity functions --- project/Boilerplate.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 111f6f542d..50f03432ce 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -479,8 +479,7 @@ object Boilerplate { if (arity == 1) s"def parFlatMap[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatMap($tupleArgs)(f)" else - s"def parFlatMapN[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = p.flatMap.flatten(Parallel.parMap$arity($tupleArgs)(f))" - + s"def parFlatMapN[Z](f: (${`A..N`}) => M[Z])(implicit p: NonEmptyParallel[M]): M[Z] = Parallel.parFlatMap$arity($tupleArgs)(f)" val parTupled = if (arity == 1) "" else From adbb470db7aeeb9b20ea12060cc54e7edc492fb9 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Wed, 28 Sep 2022 14:19:38 +0200 Subject: [PATCH 7/9] Rewrote test for clarity --- tests/shared/src/test/scala/cats/tests/ParallelSuite.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala index cb97fed9ae..5d7294e7bc 100644 --- a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala @@ -416,8 +416,8 @@ class ParallelSuite test("ParMapN over f should be consistent with parFlatMapN over f lifted in Kleisli") { forAll { (as: List[Int], bs: List[Int]) => - val f: (Int, Int) => Int = _ + _ - val mf: (Int, Int) => List[Int] = Function.untupled(Kleisli.fromFunction[List, (Int, Int)](f.tupled).run) + val f: (Int, Int) => Int = (a, b) => a + b + val mf: (Int, Int) => List[Int] = (a, b) => List(a + b) assert((as, bs).parMapN(f) == (as, bs).parFlatMapN(mf)) } } From c8b6cd0f3f468cff4ba459c21f06a5705f61b952 Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Wed, 28 Sep 2022 21:13:11 +0200 Subject: [PATCH 8/9] Autogenerating the test function itself --- tests/shared/src/test/scala/cats/tests/ParallelSuite.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala index 5d7294e7bc..1e2fa832fc 100644 --- a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala @@ -414,10 +414,9 @@ class ParallelSuite } } - test("ParMapN over f should be consistent with parFlatMapN over f lifted in Kleisli") { - forAll { (as: List[Int], bs: List[Int]) => - val f: (Int, Int) => Int = (a, b) => a + b - val mf: (Int, Int) => List[Int] = (a, b) => List(a + b) + test("ParMapN over f should be consistent with parFlatMapN over f lifted in List") { + forAll { (as: List[Int], bs: List[Int], f: (Int, Int) => Int) => + val mf: (Int, Int) => List[Int] = (a, b) => f(a, b).pure[List] assert((as, bs).parMapN(f) == (as, bs).parFlatMapN(mf)) } } From 325b4b4256a633c4e93822706d869108f3cc7cea Mon Sep 17 00:00:00 2001 From: Antonio Gelameris Date: Thu, 6 Oct 2022 08:53:33 +0200 Subject: [PATCH 9/9] Generalizing all the parFlatMap tests --- .../test/scala/cats/tests/ParallelSuite.scala | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala index 1e2fa832fc..869b2611b5 100644 --- a/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala +++ b/tests/shared/src/test/scala/cats/tests/ParallelSuite.scala @@ -391,26 +391,27 @@ class ParallelSuite } test("ParFlatMapN over List should be consistent with parMapN flatten") { - forAll { (as: List[Int], bs: List[Int], cs: List[Int]) => - val mf: (Int, Int, Int) => List[Int] = _ :: _ :: _ :: Nil + forAll { (as: List[Int], bs: List[Int], cs: List[Int], mf: (Int, Int, Int) => List[Int]) => assert((as, bs, cs).parFlatMapN(mf) == (as, bs, cs).parMapN(mf).flatten) } } test("ParFlatMapN over NonEmptyList should be consistent with parMapN flatten") { - forAll { (as: NonEmptyList[Int], bs: NonEmptyList[Int], cs: NonEmptyList[Int]) => - val mf: (Int, Int, Int) => NonEmptyList[Int] = (a, b, c) => NonEmptyList.of(a, b, c) - assert((as, bs, cs).parFlatMapN(mf) == (as, bs, cs).parMapN(mf).flatten) + forAll { + (as: NonEmptyList[Int], bs: NonEmptyList[Int], cs: NonEmptyList[Int], mf: (Int, Int, Int) => NonEmptyList[Int]) => + assert((as, bs, cs).parFlatMapN(mf) == (as, bs, cs).parMapN(mf).flatten) } } test("ParFlatMap over List should be consistent with flatmap") { - forAll { as: List[Int] => assert(Tuple1(as).parFlatMap(_ :: Nil) == Tuple1(as).flatMap(_ :: Nil)) } + forAll { (as: List[Int], mf: Int => List[Int]) => + assert(Tuple1(as).parFlatMap(mf) == Tuple1(as).flatMap(mf)) + } } test("ParFlatMap over NonEmptyList should be consistent with flatmap") { - forAll { as: NonEmptyList[Int] => - assert(Tuple1(as).parFlatMap(NonEmptyList.one) == Tuple1(as).flatMap(NonEmptyList.one)) + forAll { (as: NonEmptyList[Int], mf: Int => NonEmptyList[Int]) => + assert(Tuple1(as).parFlatMap(mf) == Tuple1(as).flatMap(mf)) } }