Skip to content

Commit a0aa6e0

Browse files
authored
Merge pull request #1112 from DavidGregory084/tuple-syntax
Syntax for Cartesian operations with tuples
2 parents ce70923 + b6bd29a commit a0aa6e0

File tree

5 files changed

+103
-6
lines changed

5 files changed

+103
-6
lines changed

core/src/main/scala/cats/syntax/all.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ trait AllSyntax
4040
with TransLiftSyntax
4141
with TraverseFilterSyntax
4242
with TraverseSyntax
43+
with TupleSyntax
4344
with ValidatedSyntax
4445
with WriterSyntax
4546
with XorSyntax

core/src/main/scala/cats/syntax/package.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ package object syntax {
3939
object transLift extends TransLiftSyntax
4040
object traverse extends TraverseSyntax
4141
object traverseFilter extends TraverseFilterSyntax
42+
object tuple extends TupleSyntax
4243
object validated extends ValidatedSyntax
4344
object writer extends WriterSyntax
4445
object xor extends XorSyntax
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package cats
2+
package syntax
3+
4+
trait TupleSyntax extends TupleCartesianSyntax

project/Boilerplate.scala

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ object Boilerplate {
2727
val templates: Seq[Template] = Seq(
2828
GenCartesianBuilders,
2929
GenCartesianArityFunctions,
30-
GenApplyArityFunctions
30+
GenApplyArityFunctions,
31+
GenTupleCartesianSyntax
3132
)
3233

3334
val header = "// auto-generated boilerplate" // TODO: put something meaningful here?
@@ -60,12 +61,21 @@ object Boilerplate {
6061
def content(tv: TemplateVals): String
6162
def range = 1 to maxArity
6263
def body: String = {
63-
val headerLines = header split '\n'
64+
def expandInstances(contents: IndexedSeq[Array[String]], acc: Array[String] = Array.empty): Array[String] =
65+
if (!contents.exists(_ exists(_ startsWith "-")))
66+
acc map (_.tail)
67+
else {
68+
val pre = contents.head takeWhile (_ startsWith "|")
69+
val instances = contents flatMap {_ dropWhile (_ startsWith "|") takeWhile (_ startsWith "-") }
70+
val next = contents map {_ dropWhile (_ startsWith "|") dropWhile (_ startsWith "-") }
71+
expandInstances(next, acc ++ pre ++ instances)
72+
}
73+
6474
val rawContents = range map { n => content(new TemplateVals(n)) split '\n' filterNot (_.isEmpty) }
65-
val preBody = rawContents.head takeWhile (_ startsWith "|") map (_.tail)
66-
val instances = rawContents flatMap {_ filter (_ startsWith "-") map (_.tail) }
67-
val postBody = rawContents.head dropWhile (_ startsWith "|") dropWhile (_ startsWith "-") map (_.tail)
68-
(headerLines ++ preBody ++ instances ++ postBody) mkString "\n"
75+
val headerLines = header split '\n'
76+
val instances = expandInstances(rawContents)
77+
val footerLines = rawContents.head.reverse.takeWhile(_ startsWith "|").map(_.tail).reverse
78+
(headerLines ++ instances ++ footerLines) mkString "\n"
6979
}
7080
}
7181

@@ -211,4 +221,52 @@ object Boilerplate {
211221
}
212222
}
213223

224+
object GenTupleCartesianSyntax extends Template {
225+
def filename(root: File) = root / "cats" / "syntax" / "TupleCartesianSyntax.scala"
226+
227+
def content(tv: TemplateVals) = {
228+
import tv._
229+
230+
val tpes = synTypes map { tpe => s"F[$tpe]" }
231+
val tpesString = tpes mkString ", "
232+
233+
val tuple = s"Tuple$arity[$tpesString]"
234+
val tupleTpe = s"t$arity: $tuple"
235+
val tupleArgs = (1 to arity) map { case n => s"t$arity._$n" } mkString ", "
236+
237+
val n = if (arity == 1) { "" } else { arity.toString }
238+
239+
val map =
240+
if (arity == 1) s"def map[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F]): F[Z] = functor.map($tupleArgs)(f)"
241+
else s"def map$arity[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F], cartesian: Cartesian[F]): F[Z] = Cartesian.map$arity($tupleArgs)(f)"
242+
243+
val contramap =
244+
if (arity == 1) s"def contramap[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F]): F[Z] = contravariant.contramap($tupleArgs)(f)"
245+
else s"def contramap$arity[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F], cartesian: Cartesian[F]): F[Z] = Cartesian.contramap$arity($tupleArgs)(f)"
246+
247+
val imap =
248+
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)"
249+
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)"
250+
251+
block"""
252+
|package cats
253+
|package syntax
254+
|
255+
|import cats.functor.{Contravariant, Invariant}
256+
|
257+
|trait TupleCartesianSyntax {
258+
- implicit def catsSyntaxTuple${arity}Cartesian[F[_], ${`A..N`}]($tupleTpe): Tuple${arity}CartesianOps[F, ${`A..N`}] = new Tuple${arity}CartesianOps(t$arity)
259+
|}
260+
|
261+
-private[syntax] final class Tuple${arity}CartesianOps[F[_], ${`A..N`}]($tupleTpe) {
262+
- $map
263+
- $contramap
264+
- $imap
265+
- def apWith[Z](f: F[(${`A..N`}) => Z])(implicit apply: Apply[F]): F[Z] = apply.ap$n(f)($tupleArgs)
266+
-}
267+
|
268+
"""
269+
}
270+
}
271+
214272
}

tests/src/test/scala/cats/tests/SyntaxTests.scala

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,39 @@ object SyntaxTests extends AllInstances with AllSyntax {
257257
val pfegea = mock[PartialFunction[E, G[A]]]
258258
val gea4 = ga.recoverWith(pfegea)
259259
}
260+
261+
def testTupleArity[F[_]: Apply : Cartesian, G[_]: Contravariant : Cartesian, H[_]: Invariant : Cartesian, A, B, C, D, E, Z] = {
262+
val tfabc = mock[(F[A], F[B], F[C])]
263+
val fa = mock[F[A]]
264+
val fb = mock[F[B]]
265+
val fc = mock[F[C]]
266+
val f = mock[(A, B, C) => Z]
267+
val ff = mock[F[(A, B, C) => Z]]
268+
269+
tfabc map3 f
270+
(fa, fb, fc) map3 f
271+
(fa, fb, fc) apWith ff
272+
273+
val tgabc = mock[(G[A], G[B])]
274+
val ga = mock[G[A]]
275+
val gb = mock[G[B]]
276+
val g = mock[Z => (A, B)]
277+
278+
tgabc contramap2 g
279+
(ga, gb) contramap2 g
280+
281+
val thabcde = mock[(H[A], H[B], H[C], H[D], H[E])]
282+
val ha = mock[H[A]]
283+
val hb = mock[H[B]]
284+
val hc = mock[H[C]]
285+
val hd = mock[H[D]]
286+
val he = mock[H[E]]
287+
val f5 = mock[(A, B, C, D, E) => Z]
288+
val g5 = mock[Z => (A, B, C, D, E)]
289+
290+
thabcde.imap5(f5)(g5)
291+
(ha, hb, hc, hd, he).imap5(f5)(g5)
292+
}
260293
}
261294

262295
/**

0 commit comments

Comments
 (0)