Skip to content

Commit

Permalink
Syntax for Cartesian operations with tuples
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidGregory084 committed Aug 7, 2016
1 parent f212a12 commit df80438
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 6 deletions.
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ trait AllSyntax
with TransLiftSyntax
with TraverseFilterSyntax
with TraverseSyntax
with TupleSyntax
with ValidatedSyntax
with WriterSyntax
with XorSyntax
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/syntax/tuple.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cats
package syntax

trait TupleSyntax extends TupleCartesianSyntax
70 changes: 64 additions & 6 deletions project/Boilerplate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ object Boilerplate {
val templates: Seq[Template] = Seq(
GenCartesianBuilders,
GenCartesianArityFunctions,
GenApplyArityFunctions
GenApplyArityFunctions,
GenTupleCartesianSyntax
)

val header = "// auto-generated boilerplate" // TODO: put something meaningful here?
Expand Down Expand Up @@ -60,12 +61,21 @@ object Boilerplate {
def content(tv: TemplateVals): String
def range = 1 to maxArity
def body: String = {
val headerLines = header split '\n'
def expandInstances(contents: IndexedSeq[Array[String]], acc: Array[String] = Array.empty): Array[String] =
if (!contents.exists(_ exists(_ startsWith "-")))
acc map (_.tail)
else {
val pre = contents.head takeWhile (_ startsWith "|")
val instances = contents flatMap {_ dropWhile (_ startsWith "|") takeWhile (_ startsWith "-") }
val next = contents map {_ dropWhile (_ startsWith "|") dropWhile (_ startsWith "-") }
expandInstances(next, acc ++ pre ++ instances)
}

val rawContents = range map { n => content(new TemplateVals(n)) split '\n' filterNot (_.isEmpty) }
val preBody = rawContents.head takeWhile (_ startsWith "|") map (_.tail)
val instances = rawContents flatMap {_ filter (_ startsWith "-") map (_.tail) }
val postBody = rawContents.head dropWhile (_ startsWith "|") dropWhile (_ startsWith "-") map (_.tail)
(headerLines ++ preBody ++ instances ++ postBody) mkString "\n"
val headerLines = header split '\n'
val instances = expandInstances(rawContents)
val footerLines = rawContents.head.reverse.takeWhile(_ startsWith "|").map(_.tail).reverse
(headerLines ++ instances ++ footerLines) mkString "\n"
}
}

Expand Down Expand Up @@ -211,4 +221,52 @@ object Boilerplate {
}
}

object GenTupleCartesianSyntax extends Template {
def filename(root: File) = root / "cats" / "syntax" / "TupleCartesianSyntax.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 TupleCartesianSyntax {
- implicit def catsSyntaxTuple${arity}Cartesian[F[_], ${`A..N`}]($tupleTpe): Tuple${arity}CartesianOps[F, ${`A..N`}] = new Tuple${arity}CartesianOps(t$arity)
|}
|
-private[syntax] final class Tuple${arity}CartesianOps[F[_], ${`A..N`}]($tupleTpe) {
- $map
- $contramap
- $imap
- def apWith[Z](f: F[(${`A..N`}) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap$n(f)($tupleArgs)
-}
|
"""
}
}

}
33 changes: 33 additions & 0 deletions tests/src/test/scala/cats/tests/SyntaxTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,37 @@ class SyntaxTests extends AllInstances with AllSyntax {
val pfegea = mock[PartialFunction[E, G[A]]]
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)
}
}

0 comments on commit df80438

Please sign in to comment.