From eefd70b99596811053cb3e8a4611b0eab99574fa Mon Sep 17 00:00:00 2001 From: David Gregory Date: Thu, 8 Dec 2016 02:16:43 +0000 Subject: [PATCH] Apply syntax for tuples, fixes #1363 --- .jvmopts | 3 - core/src/main/scala/cats/data/EitherT.scala | 2 +- core/src/main/scala/cats/syntax/all.scala | 1 - core/src/main/scala/cats/syntax/apply.scala | 2 +- core/src/main/scala/cats/syntax/package.scala | 1 - core/src/main/scala/cats/syntax/tuple.scala | 4 - docs/src/main/tut/typeclasses/applicative.md | 2 +- .../cats/free/FreeApplicativeTests.scala | 2 +- project/Boilerplate.scala | 18 +++-- .../CsvCodecInvariantMonoidalTests.scala | 4 +- .../test/scala/cats/tests/SyntaxTests.scala | 75 +++++++------------ 11 files changed, 45 insertions(+), 69 deletions(-) delete mode 100644 core/src/main/scala/cats/syntax/tuple.scala diff --git a/.jvmopts b/.jvmopts index 83ed6fb401..0ca1262c38 100644 --- a/.jvmopts +++ b/.jvmopts @@ -1,4 +1,3 @@ -# see https://weblogs.java.net/blog/kcpeppe/archive/2013/12/11/case-study-jvm-hotspot-flags -Dfile.encoding=UTF8 -Xms1G -Xmx6G @@ -6,7 +5,5 @@ -XX:ReservedCodeCacheSize=250M -XX:+TieredCompilation -XX:-UseGCOverheadLimit -# effectively adds GC to Perm space -XX:+CMSClassUnloadingEnabled -# must be enabled for CMSClassUnloadingEnabled to work -XX:+UseConcMarkSweepGC diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index e0baf6eefb..42e5ffddfe 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -175,7 +175,7 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) { * scala> val v1: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList.of("error 1")) * scala> val v2: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList.of("error 2")) * scala> val eithert: EitherT[Option, Error, Int] = EitherT(Some(Either.left("error 3"))) - * scala> eithert.withValidated { v3 => (v1 |@| v2 |@| v3.leftMap(NonEmptyList.of(_))).map{ case (i, j, k) => i + j + k } } + * scala> eithert.withValidated { v3 => (v1, v2, v3.leftMap(NonEmptyList.of(_))).mapN { case (i, j, k) => i + j + k } } * res0: EitherT[Option, NonEmptyList[Error], Int] = EitherT(Some(Left(NonEmptyList(error 1, error 2, error 3)))) * }}} */ diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index ea8cb90511..327006c532 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -42,7 +42,6 @@ trait AllSyntax with StrongSyntax with TraverseFilterSyntax with TraverseSyntax - with TupleSyntax with ValidatedSyntax with VectorSyntax with WriterSyntax diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index 17f9685aaf..32854d57c7 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -1,7 +1,7 @@ package cats package syntax -trait ApplySyntax { +trait ApplySyntax extends TupleCartesianSyntax { implicit final def catsSyntaxApply[F[_], A](fa: F[A])(implicit F: Apply[F]): Apply.Ops[F, A] = new Apply.Ops[F, A] { val self = fa diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index 2f1986e5e4..d83946f85b 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -42,7 +42,6 @@ package object syntax { object monadTrans extends MonadTransSyntax object traverse extends TraverseSyntax object traverseFilter extends TraverseFilterSyntax - object tuple extends TupleSyntax object validated extends ValidatedSyntax object vector extends VectorSyntax object writer extends WriterSyntax diff --git a/core/src/main/scala/cats/syntax/tuple.scala b/core/src/main/scala/cats/syntax/tuple.scala deleted file mode 100644 index 2448e6f3bc..0000000000 --- a/core/src/main/scala/cats/syntax/tuple.scala +++ /dev/null @@ -1,4 +0,0 @@ -package cats -package syntax - -trait TupleSyntax extends TupleCartesianSyntax diff --git a/docs/src/main/tut/typeclasses/applicative.md b/docs/src/main/tut/typeclasses/applicative.md index b4c26cf9d0..5d71066890 100644 --- a/docs/src/main/tut/typeclasses/applicative.md +++ b/docs/src/main/tut/typeclasses/applicative.md @@ -272,7 +272,7 @@ The second expects the effects in a tuple and works by enriching syntax on top o `TupleN` types. ```tut:book -(o1, o2).map2((i: Int, s: String) => i.toString ++ s) +(o1, o2).mapN((i: Int, s: String) => i.toString ++ s) ``` ## Further Reading diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index 1e6467900b..afdef68074 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -73,7 +73,7 @@ class FreeApplicativeTests extends CatsSuite { // fixed by #568 val fli1 = FreeApplicative.lift[List, Int](List(1, 3, 5, 7)) val fli2 = FreeApplicative.lift[List, Int](List(1, 3, 5, 7)) - (fli1 |@| fli2).map(_ + _) + (fli1, fli2).mapN(_ + _) } test("FreeApplicative#analyze") { diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index bbc587aecc..7168d4087a 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -238,15 +238,21 @@ object Boilerplate { val map = if (arity == 1) s"def map[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F]): F[Z] = functor.map($tupleArgs)(f)" - else s"def map$arity[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F], cartesian: Cartesian[F]): F[Z] = Cartesian.map$arity($tupleArgs)(f)" + else s"def mapN[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F]): F[Z] = Cartesian.map$arity($tupleArgs)(f)" val contramap = if (arity == 1) s"def contramap[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F]): F[Z] = contravariant.contramap($tupleArgs)(f)" - else s"def contramap$arity[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.contramap$arity($tupleArgs)(f)" + else s"def contramapN[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F]): F[Z] = Cartesian.contramap$arity($tupleArgs)(f)" val imap = if (arity == 1) s"def imap[Z](f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit invariant: Invariant[F]): F[Z] = invariant.imap($tupleArgs)(f)(g)" - else s"def imap$arity[Z](f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit invariant: Invariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.imap$arity($tupleArgs)(f)(g)" + else s"def imapN[Z](f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit invariant: Invariant[F]): F[Z] = Cartesian.imap$arity($tupleArgs)(f)(g)" + + val tupled = if (arity != 1) { + s"def tupled(implicit invariant: Invariant[F]): F[(${`A..N`})] = Cartesian.tuple$n($tupleArgs)" + } else { + "" + } block""" |package cats @@ -255,13 +261,15 @@ object Boilerplate { |import cats.functor.{Contravariant, Invariant} | |trait TupleCartesianSyntax { - - implicit def catsSyntaxTuple${arity}Cartesian[F[_], ${`A..N`}]($tupleTpe): Tuple${arity}CartesianOps[F, ${`A..N`}] = new Tuple${arity}CartesianOps(t$arity) + - implicit def catsSyntaxTuple${arity}Cartesian[F[_], ${`A..N`}]($tupleTpe)(implicit C: Cartesian[F]): Tuple${arity}CartesianOps[F, ${`A..N`}] = new Tuple${arity}CartesianOps(t$arity, C) |} | - -private[syntax] final class Tuple${arity}CartesianOps[F[_], ${`A..N`}]($tupleTpe) { + -private[syntax] final class Tuple${arity}CartesianOps[F[_], ${`A..N`}]($tupleTpe, C: Cartesian[F]) { + - implicit val cartesian: Cartesian[F] = C - $map - $contramap - $imap + - $tupled - def apWith[Z](f: F[(${`A..N`}) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap$n(f)($tupleArgs) -} | diff --git a/tests/src/test/scala/cats/tests/CsvCodecInvariantMonoidalTests.scala b/tests/src/test/scala/cats/tests/CsvCodecInvariantMonoidalTests.scala index 3363c32fc8..9137f8d174 100644 --- a/tests/src/test/scala/cats/tests/CsvCodecInvariantMonoidalTests.scala +++ b/tests/src/test/scala/cats/tests/CsvCodecInvariantMonoidalTests.scala @@ -4,7 +4,7 @@ package tests import cats.laws.discipline.eq.catsLawsEqForFn1 import cats.laws.discipline.{InvariantMonoidalTests, SerializableTests} import cats.instances.all._ -import cats.syntax.cartesian._ +import cats.syntax.apply._ import cats.Eq import org.scalacheck.{Arbitrary, Gen} @@ -42,7 +42,7 @@ object CsvCodecInvariantMonoidalTests { def read(s: CSV): (Option[(A, B)], CSV) = { val (a1, s1) = fa.read(s) val (a2, s2) = fb.read(s1) - ((a1 |@| a2).map(_ -> _), s2) + ((a1, a2).mapN(_ -> _), s2) } def write(a: (A, B)): CSV = diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index ec2a18f8ec..0abe44ccf7 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -180,28 +180,37 @@ object SyntaxTests extends AllInstances with AllSyntax { val fb1: F[B] = fa.as(b) } - def testApply[F[_]: Apply, A, B, C, D, Z]: Unit = { + def testApply[F[_]: Apply : Cartesian, G[_]: Contravariant : Cartesian, H[_]: Invariant : Cartesian, A, B, C, D, E, Z] = { + val tfabc = mock[(F[A], F[B], F[C])] val fa = mock[F[A]] - val fab = mock[F[A => B]] - val fb0: F[B] = fab.ap(fa) - val fb = mock[F[B]] - val fabz = mock[F[(A, B) => Z]] - val fz0: F[Z] = fabz.ap2(fa, fb) + val fc = mock[F[C]] + val f = mock[(A, B, C) => Z] + val ff = mock[F[(A, B, C) => Z]] + + tfabc mapN f + (fa, fb, fc) mapN f + (fa, fb, fc) apWith ff - val f = mock[(A, B) => Z] - val fz1: F[Z] = fa.map2(fb)(f) + val tgabc = mock[(G[A], G[B])] + val ga = mock[G[A]] + val gb = mock[G[B]] + val g = mock[Z => (A, B)] - val f1 = mock[(A, B) => Z] - val ff1 = mock[F[(A, B) => Z]] - val fz2: F[Z] = (fa |@| fb).map(f1) - val fz3: F[Z] = (fa |@| fb).apWith(ff1) + tgabc contramapN g + (ga, gb) contramapN g - val fc = mock[F[C]] - val f2 = mock[(A, B, C) => Z] - val ff2 = mock[F[(A, B, C) => Z]] - val fz4: F[Z] = (fa |@| fb |@| fc).map(f2) - val fz5: F[Z] = (fa |@| fb |@| fc).apWith(ff2) + val thabcde = mock[(H[A], H[B], H[C], H[D], H[E])] + val ha = mock[H[A]] + val hb = mock[H[B]] + val hc = mock[H[C]] + val hd = mock[H[D]] + val he = mock[H[E]] + val f5 = mock[(A, B, C, D, E) => Z] + val g5 = mock[Z => (A, B, C, D, E)] + + thabcde.imapN(f5)(g5) + (ha, hb, hc, hd, he).imapN(f5)(g5) } def testBifoldable[F[_, _]: Bifoldable, A, B, C, D: Monoid]: Unit = { @@ -274,38 +283,6 @@ object SyntaxTests extends AllInstances with AllSyntax { val gea4 = ga.recoverWith(pfegea) } - def testTupleArity[F[_]: Apply : Cartesian, G[_]: Contravariant : Cartesian, H[_]: Invariant : Cartesian, A, B, C, D, E, Z] = { - val tfabc = mock[(F[A], F[B], F[C])] - val fa = mock[F[A]] - val fb = mock[F[B]] - val fc = mock[F[C]] - val f = mock[(A, B, C) => Z] - val ff = mock[F[(A, B, C) => Z]] - - tfabc map3 f - (fa, fb, fc) map3 f - (fa, fb, fc) apWith ff - - val tgabc = mock[(G[A], G[B])] - val ga = mock[G[A]] - val gb = mock[G[B]] - val g = mock[Z => (A, B)] - - tgabc contramap2 g - (ga, gb) contramap2 g - - val thabcde = mock[(H[A], H[B], H[C], H[D], H[E])] - val ha = mock[H[A]] - val hb = mock[H[B]] - val hc = mock[H[C]] - val hd = mock[H[D]] - val he = mock[H[E]] - val f5 = mock[(A, B, C, D, E) => Z] - val g5 = mock[Z => (A, B, C, D, E)] - - thabcde.imap5(f5)(g5) - (ha, hb, hc, hd, he).imap5(f5)(g5) - } } /**