From afe548abc6f010705e9673f6809dccc8b6d23f06 Mon Sep 17 00:00:00 2001 From: David Gregory Date: Sat, 11 Jun 2016 00:42:13 +0100 Subject: [PATCH] Syntax for Cartesian operations with tuples --- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/tuple.scala | 4 ++ project/Boilerplate.scala | 48 ++++++++++++++++++- .../test/scala/cats/tests/SyntaxTests.scala | 31 ++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 core/src/main/scala/cats/syntax/tuple.scala diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index e3250451305..0f648574b07 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -36,6 +36,7 @@ trait AllSyntax with StrongSyntax with TransLiftSyntax with TraverseSyntax + with TupleSyntax with XorSyntax with ValidatedSyntax with CoproductSyntax diff --git a/core/src/main/scala/cats/syntax/tuple.scala b/core/src/main/scala/cats/syntax/tuple.scala new file mode 100644 index 00000000000..cfef997af07 --- /dev/null +++ b/core/src/main/scala/cats/syntax/tuple.scala @@ -0,0 +1,4 @@ +package cats +package syntax + +trait TupleSyntax extends TupleAritySyntax diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index 1a9cd989eb1..3e802ab756c 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -27,7 +27,8 @@ object Boilerplate { val templates: Seq[Template] = Seq( GenCartesianBuilders, GenCartesianArityFunctions, - GenApplyArityFunctions + GenApplyArityFunctions, + GenTupleAritySyntax ) val header = "// auto-generated boilerplate" // TODO: put something meaningful here? @@ -211,4 +212,49 @@ object Boilerplate { } } + object GenTupleAritySyntax extends Template { + def filename(root: File) = root / "cats" / "syntax" / "TupleAritySyntax.scala" + + def content(tv: TemplateVals) = { + import tv._ + + val tpes = synTypes map { tpe => s"F[$tpe]" } + val tpesString = tpes mkString ", " + + val tuple = s"Tuple$arity[$tpesString]" + val tupleTpe = s"t$arity: $tuple" + val tupleArgs = (1 to arity) map { case n => s"t$arity._$n"} mkString ", " + + val n = if (arity == 1) { "" } else { arity.toString } + + 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)" + + 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)" + + 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)" + + block""" + |package cats + |package syntax + | + |import cats.functor.{Contravariant, Invariant} + | + |trait TupleAritySyntax { + - private[syntax] final class Tuple${arity}Ops[F[_], ${`A..N`}]($tupleTpe) { + - $map + - $contramap + - $imap + - } + - implicit def tuple${arity}Syntax[F[_], ${`A..N`}]($tupleTpe): Tuple${arity}Ops[F, ${`A..N`}] = new Tuple${arity}Ops(t$arity) + |} + """ + } + } + } diff --git a/tests/src/test/scala/cats/tests/SyntaxTests.scala b/tests/src/test/scala/cats/tests/SyntaxTests.scala index 8228c73e685..d6870971935 100644 --- a/tests/src/test/scala/cats/tests/SyntaxTests.scala +++ b/tests/src/test/scala/cats/tests/SyntaxTests.scala @@ -254,4 +254,35 @@ class SyntaxTests extends AllInstances with AllSyntax { val pfegea = mock[PartialFunction[E, G[A]]] val gea4 = ga.recoverWith(pfegea) } + + def testTupleArity[F[_]: Functor : 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] + + tfabc map3 f + (fa, fb, fc) map3 f + + 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) + } }