From ff5b4c476ea376aa2b86496cde420f4041de1b62 Mon Sep 17 00:00:00 2001 From: Julien Richard-Foy Date: Fri, 2 Oct 2015 18:12:41 +0200 Subject: [PATCH] Generalize ApplyBuilder to Monoidal. --- core/src/main/scala/cats/Apply.scala | 13 +- core/src/main/scala/cats/Functor.scala | 12 +- core/src/main/scala/cats/Monoidal.scala | 13 ++ core/src/main/scala/cats/data/Streaming.scala | 2 +- .../scala/cats/functor/Contravariant.scala | 12 +- .../main/scala/cats/functor/Invariant.scala | 14 ++- core/src/main/scala/cats/package.scala | 2 +- core/src/main/scala/cats/std/list.scala | 2 +- core/src/main/scala/cats/std/map.scala | 2 +- core/src/main/scala/cats/std/option.scala | 2 +- core/src/main/scala/cats/std/stream.scala | 2 +- core/src/main/scala/cats/std/vector.scala | 2 +- core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/apply.scala | 22 +--- .../src/main/scala/cats/syntax/monoidal.scala | 28 +++++ core/src/main/scala/cats/syntax/package.scala | 1 + .../main/scala/cats/laws/MonoidalLaws.scala | 16 +++ project/Boilerplate.scala | 115 ++++++++++++++---- .../test/scala/cats/state/StateTTests.scala | 2 +- 19 files changed, 198 insertions(+), 65 deletions(-) create mode 100644 core/src/main/scala/cats/Monoidal.scala create mode 100644 core/src/main/scala/cats/syntax/monoidal.scala create mode 100644 laws/src/main/scala/cats/laws/MonoidalLaws.scala diff --git a/core/src/main/scala/cats/Apply.scala b/core/src/main/scala/cats/Apply.scala index 99f3e0b6d7d..b40c8d9dd71 100644 --- a/core/src/main/scala/cats/Apply.scala +++ b/core/src/main/scala/cats/Apply.scala @@ -8,7 +8,7 @@ import simulacrum.typeclass * Must obey the laws defined in cats.laws.ApplyLaws. */ @typeclass(excludeParents=List("ApplyArityFunctions")) -trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self => +trait Apply[F[_]] extends Functor[F] with Monoidal[F] with ApplyArityFunctions[F] { self => /** * Given a value and a function in the Apply context, applies the @@ -22,14 +22,6 @@ trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self => def ap2[A, B, Z](fa: F[A], fb: F[B])(f: F[(A, B) => Z]): F[Z] = ap(fb)(ap(fa)(map(f)(f => (a: A) => (b: B) => f(a, b)))) - /** - * Applies the pure (binary) function f to the effectful values fa and fb. - * - * map2 can be seen as a binary version of [[cats.Functor]]#map. - */ - def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z): F[Z] = - ap(fb)(map(fa)(a => (b: B) => f(a, b))) - /** * Two sequentially dependent Applys can be composed. * @@ -45,6 +37,9 @@ trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self => def F: Apply[F] = self def G: Apply[G] = GG } + + override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] = ap(fb)(map(fa)(a => b => (a, b))) + } trait CompositeApply[F[_], G[_]] diff --git a/core/src/main/scala/cats/Functor.scala b/core/src/main/scala/cats/Functor.scala index ed35aa0db5c..b02090d42f8 100644 --- a/core/src/main/scala/cats/Functor.scala +++ b/core/src/main/scala/cats/Functor.scala @@ -10,7 +10,8 @@ import functor.Contravariant * * Must obey the laws defined in cats.laws.FunctorLaws. */ -@typeclass trait Functor[F[_]] extends functor.Invariant[F] { self => +@typeclass(excludeParents=List("FunctorArityFunctions")) +trait Functor[F[_]] extends functor.Invariant[F] with FunctorArityFunctions[F] { self => def map[A, B](fa: F[A])(f: A => B): F[B] def imap[A, B](fa: F[A])(f: A => B)(fi: B => A): F[B] = map(fa)(f) @@ -60,6 +61,15 @@ import functor.Contravariant * Replaces the `A` value in `F[A]` with the supplied value. */ def as[A, B](fa: F[A], b: B): F[B] = map(fa)(_ => b) + + /** + * Applies the pure (binary) function f to the effectful values fa and fb. + * + * map2 can be seen as a binary version of [[cats.Functor]]#map. + */ + def map2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z)(implicit F: Monoidal[F]): F[Z] = + map(F.product(fa, fb))(f.tupled) + } object Functor { diff --git a/core/src/main/scala/cats/Monoidal.scala b/core/src/main/scala/cats/Monoidal.scala new file mode 100644 index 00000000000..f59262b3456 --- /dev/null +++ b/core/src/main/scala/cats/Monoidal.scala @@ -0,0 +1,13 @@ +package cats + +import simulacrum.typeclass + +/** + * Monoidal allows us to express uncurried function application within a context, + * whatever the context variance is. + * + * It is worth noting that the couple Monoidal and [[Functor]] is interdefinable with [[Apply]]. + */ +@typeclass trait Monoidal[F[_]] { + def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] +} diff --git a/core/src/main/scala/cats/data/Streaming.scala b/core/src/main/scala/cats/data/Streaming.scala index 4a3598073af..8a2684a38d5 100644 --- a/core/src/main/scala/cats/data/Streaming.scala +++ b/core/src/main/scala/cats/data/Streaming.scala @@ -887,7 +887,7 @@ trait StreamingInstances extends StreamingInstances1 { def combine[A](xs: Streaming[A], ys: Streaming[A]): Streaming[A] = xs concat ys - override def map2[A, B, Z](fa: Streaming[A], fb: Streaming[B])(f: (A, B) => Z): Streaming[Z] = + override def map2[A, B, Z](fa: Streaming[A], fb: Streaming[B])(f: (A, B) => Z)(implicit ev: Monoidal[Streaming]): Streaming[Z] = fa.flatMap(a => fb.map(b => f(a, b))) def coflatMap[A, B](fa: Streaming[A])(f: Streaming[A] => B): Streaming[B] = diff --git a/core/src/main/scala/cats/functor/Contravariant.scala b/core/src/main/scala/cats/functor/Contravariant.scala index b8bec7ee01e..4aa31eaa8a2 100644 --- a/core/src/main/scala/cats/functor/Contravariant.scala +++ b/core/src/main/scala/cats/functor/Contravariant.scala @@ -6,7 +6,8 @@ import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.ContravariantLaws. */ -@typeclass trait Contravariant[F[_]] extends Invariant[F] { self => +@typeclass(excludeParents=List("ContravariantArityFunctions")) +trait Contravariant[F[_]] extends Invariant[F] with ContravariantArityFunctions[F] { self => def contramap[A, B](fa: F[A])(f: B => A): F[B] override def imap[A, B](fa: F[A])(f: A => B)(fi: B => A): F[B] = contramap(fa)(fi) @@ -25,6 +26,15 @@ import simulacrum.typeclass def G: Functor[G] = G0 } } + + /** + * Applies the pure function f to the effectful values fb and fb. + * + * contramap2 can be seen as a binary version of [[cats.functor.Contravariant]]#contramap. + */ + def contramap2[A, B, Z](fa: F[A], fb: F[B])(f: Z => (A, B))(implicit F: Monoidal[F]): F[Z] = + contramap(F.product(fa, fb))(f) + } object Contravariant { diff --git a/core/src/main/scala/cats/functor/Invariant.scala b/core/src/main/scala/cats/functor/Invariant.scala index 437dd8103b6..ebf0af19199 100644 --- a/core/src/main/scala/cats/functor/Invariant.scala +++ b/core/src/main/scala/cats/functor/Invariant.scala @@ -6,7 +6,8 @@ import simulacrum.typeclass /** * Must obey the laws defined in cats.laws.InvariantLaws. */ -@typeclass trait Invariant[F[_]] extends Any with Serializable { self => +@typeclass(excludeParents=List("InvariantArityFunctions")) +trait Invariant[F[_]] extends InvariantArityFunctions[F] with Serializable { self => def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] /** @@ -32,6 +33,17 @@ import simulacrum.typeclass def F: Invariant[F] = self def G: Contravariant[G] = GG } + + /** + * Applies the pure functions f and g to the effectful values fa and fb. + * + * imap2 can be seen as a binary version of [[cats.functor.Invariant]]#imap. + */ + def imap2[A, B, Z](fa: F[A], fb: F[B])(f: (A, B) => Z)(g: Z => (A, B))(implicit F: Monoidal[F]): F[Z] = + imap(F.product(fa, fb))(f.tupled)(g) + + def tuple2[A, B](fa: F[A], fb: F[B])(implicit F: Monoidal[F]): F[(A, B)] = F.product(fa, fb) + } object Invariant extends AlgebraInvariantInstances { diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 3830de64f6a..34f508604bf 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -35,7 +35,7 @@ package object cats { override def map[A, B](fa: A)(f: A => B): B = f(fa) override def ap[A, B](fa: A)(ff: A => B): B = ff(fa) override def flatten[A](ffa: A): A = ffa - override def map2[A, B, Z](fa: A, fb: B)(f: (A, B) => Z): Z = f(fa, fb) + override def map2[A, B, Z](fa: A, fb: B)(f: (A, B) => Z)(implicit ev: Monoidal[Id]): Z = f(fa, fb) override def lift[A, B](f: A => B): A => B = f override def imap[A, B](fa: A)(f: A => B)(fi: B => A): B = f(fa) } diff --git a/core/src/main/scala/cats/std/list.scala b/core/src/main/scala/cats/std/list.scala index 5eb190eb548..89dcee73f3c 100644 --- a/core/src/main/scala/cats/std/list.scala +++ b/core/src/main/scala/cats/std/list.scala @@ -26,7 +26,7 @@ trait ListInstances extends ListInstances1 { def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] = fa.flatMap(f) - override def map2[A, B, Z](fa: List[A], fb: List[B])(f: (A, B) => Z): List[Z] = + override def map2[A, B, Z](fa: List[A], fb: List[B])(f: (A, B) => Z)(implicit ev: Monoidal[List]): List[Z] = fa.flatMap(a => fb.map(b => f(a, b))) def coflatMap[A, B](fa: List[A])(f: List[A] => B): List[B] = { diff --git a/core/src/main/scala/cats/std/map.scala b/core/src/main/scala/cats/std/map.scala index bb6e2b35e08..926c06fc140 100644 --- a/core/src/main/scala/cats/std/map.scala +++ b/core/src/main/scala/cats/std/map.scala @@ -42,7 +42,7 @@ trait MapInstances extends algebra.std.MapInstances { override def map[A, B](fa: Map[K, A])(f: A => B): Map[K, B] = fa.map { case (k, a) => (k, f(a)) } - override def map2[A, B, Z](fa: Map[K, A], fb: Map[K, B])(f: (A, B) => Z): Map[K, Z] = + override def map2[A, B, Z](fa: Map[K, A], fb: Map[K, B])(f: (A, B) => Z)(implicit ev: Monoidal[Map[K, ?]]): Map[K, Z] = fa.flatMap { case (k, a) => fb.get(k).map(b => (k, f(a, b))) } override def ap[A, B](fa: Map[K, A])(ff: Map[K, A => B]): Map[K, B] = diff --git a/core/src/main/scala/cats/std/option.scala b/core/src/main/scala/cats/std/option.scala index 9eab6351c3e..7b8a88dbb40 100644 --- a/core/src/main/scala/cats/std/option.scala +++ b/core/src/main/scala/cats/std/option.scala @@ -19,7 +19,7 @@ trait OptionInstances extends OptionInstances1 { def flatMap[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = fa.flatMap(f) - override def map2[A, B, Z](fa: Option[A], fb: Option[B])(f: (A, B) => Z): Option[Z] = + override def map2[A, B, Z](fa: Option[A], fb: Option[B])(f: (A, B) => Z)(implicit ev: Monoidal[Option]): Option[Z] = fa.flatMap(a => fb.map(b => f(a, b))) def coflatMap[A, B](fa: Option[A])(f: Option[A] => B): Option[B] = diff --git a/core/src/main/scala/cats/std/stream.scala b/core/src/main/scala/cats/std/stream.scala index 988748b09de..d640604d0e2 100644 --- a/core/src/main/scala/cats/std/stream.scala +++ b/core/src/main/scala/cats/std/stream.scala @@ -19,7 +19,7 @@ trait StreamInstances { def flatMap[A, B](fa: Stream[A])(f: A => Stream[B]): Stream[B] = fa.flatMap(f) - override def map2[A, B, Z](fa: Stream[A], fb: Stream[B])(f: (A, B) => Z): Stream[Z] = + override def map2[A, B, Z](fa: Stream[A], fb: Stream[B])(f: (A, B) => Z)(implicit ev: Monoidal[Stream]): Stream[Z] = fa.flatMap(a => fb.map(b => f(a, b))) def coflatMap[A, B](fa: Stream[A])(f: Stream[A] => B): Stream[B] = diff --git a/core/src/main/scala/cats/std/vector.scala b/core/src/main/scala/cats/std/vector.scala index 9f88565c016..872bb61d689 100644 --- a/core/src/main/scala/cats/std/vector.scala +++ b/core/src/main/scala/cats/std/vector.scala @@ -19,7 +19,7 @@ trait VectorInstances { def flatMap[A, B](fa: Vector[A])(f: A => Vector[B]): Vector[B] = fa.flatMap(f) - override def map2[A, B, Z](fa: Vector[A], fb: Vector[B])(f: (A, B) => Z): Vector[Z] = + override def map2[A, B, Z](fa: Vector[A], fb: Vector[B])(f: (A, B) => Z)(implicit ev: Monoidal[Vector]): Vector[Z] = fa.flatMap(a => fb.map(b => f(a, b))) def foldLeft[A, B](fa: Vector[A], b: B)(f: (B, A) => B): B = diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 08ba6c48cf0..c94fbaae9d0 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -3,6 +3,7 @@ package syntax trait AllSyntax extends ApplySyntax + with MonoidalSyntax with BifunctorSyntax with CoflatMapSyntax with ComonadSyntax diff --git a/core/src/main/scala/cats/syntax/apply.scala b/core/src/main/scala/cats/syntax/apply.scala index 95b89b0bd99..650c44c613e 100644 --- a/core/src/main/scala/cats/syntax/apply.scala +++ b/core/src/main/scala/cats/syntax/apply.scala @@ -2,31 +2,17 @@ package cats package syntax trait ApplySyntax1 { - implicit def applySyntaxU[FA](fa: FA)(implicit U: Unapply[Apply, FA]): ApplyOps[U.M, U.A] = - new ApplyOps[U.M, U.A] { + implicit def applySyntaxU[FA](fa: FA)(implicit U: Unapply[Apply, FA]): Apply.Ops[U.M, U.A] = + new Apply.Ops[U.M, U.A] { val self = U.subst(fa) val typeClassInstance = U.TC } } trait ApplySyntax extends ApplySyntax1 { - implicit def applySyntax[F[_], A](fa: F[A])(implicit F: Apply[F]): ApplyOps[F, A] = - new ApplyOps[F,A] { + implicit def applySyntax[F[_], A](fa: F[A])(implicit F: Apply[F]): Apply.Ops[F, A] = + new Apply.Ops[F,A] { val self = fa val typeClassInstance = F } } - -abstract class ApplyOps[F[_], A] extends Apply.Ops[F, A] { - def |@|[B](fb: F[B]): ApplyBuilder[F]#ApplyBuilder2[A, B] = new ApplyBuilder[F] |@| self |@| fb - - /** - * combine both contexts but only return the right value - */ - def *>[B](fb: F[B]): F[B] = typeClassInstance.map2(self, fb)((a,b) => b) - - /** - * combine both contexts but only return the left value - */ - def <*[B](fb: F[B]): F[A] = typeClassInstance.map2(self, fb)((a,b) => a) -} diff --git a/core/src/main/scala/cats/syntax/monoidal.scala b/core/src/main/scala/cats/syntax/monoidal.scala new file mode 100644 index 00000000000..b4126200b53 --- /dev/null +++ b/core/src/main/scala/cats/syntax/monoidal.scala @@ -0,0 +1,28 @@ +package cats +package syntax + +trait MonoidalSyntax1 { + implicit def monoidalSyntaxU[FA](fa: FA)(implicit U: Unapply[Monoidal, FA]): MonoidalOps[U.M, U.A] = + new MonoidalOps[U.M, U.A] { + val self = U.subst(fa) + val typeClassInstance = U.TC + } +} + +trait MonoidalSyntax extends MonoidalSyntax1 { + implicit def monoidalSyntax[F[_], A](fa: F[A])(implicit F: Monoidal[F]): MonoidalOps[F, A] = + new MonoidalOps[F, A] { + val self = fa + val typeClassInstance = F + } +} + +abstract class MonoidalOps[F[_], A] extends Monoidal.Ops[F, A] { + def |@|[B](fb: F[B]): MonoidalBuilder[F]#MonoidalBuilder2[A, B] = + new MonoidalBuilder[F] |@| self |@| fb + + def *>[B](fb: F[B])(implicit F: Functor[F]): F[B] = F.map(typeClassInstance.product(self, fb)) { case (a, b) => b } + + def <*[B](fb: F[B])(implicit F: Functor[F]): F[A] = F.map(typeClassInstance.product(self, fb)) { case (a, b) => a } + +} diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index f94e6d9e36d..31fd5f5c8d8 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -3,6 +3,7 @@ package cats package object syntax { object all extends AllSyntax object apply extends ApplySyntax + object monoidal extends MonoidalSyntax object bifunctor extends BifunctorSyntax object coflatMap extends CoflatMapSyntax object comonad extends ComonadSyntax diff --git a/laws/src/main/scala/cats/laws/MonoidalLaws.scala b/laws/src/main/scala/cats/laws/MonoidalLaws.scala new file mode 100644 index 00000000000..71e3de77c19 --- /dev/null +++ b/laws/src/main/scala/cats/laws/MonoidalLaws.scala @@ -0,0 +1,16 @@ +package cats.laws + +import cats.Monoidal + +trait MonoidalLaws[F[_]] { + + implicit def F: Monoidal[F] + +} + +object MonoidalLaws { + + def apply[F[_]](implicit ev: Monoidal[F]): MonoidalLaws[F] = + new MonoidalLaws[F] { def F: Monoidal[F] = ev } + +} \ No newline at end of file diff --git a/project/Boilerplate.scala b/project/Boilerplate.scala index cdb87f4690e..a9c95beafbb 100644 --- a/project/Boilerplate.scala +++ b/project/Boilerplate.scala @@ -25,8 +25,11 @@ object Boilerplate { val templates: Seq[Template] = Seq( - GenApplyBuilders, - GenApplyArityFunctions + GenMonoidalBuilders, + GenApplyArityFunctions, + GenFunctorArityFunctions, + GenContravariantArityFunctions, + GenInvariantArityFunctions ) val header = "// auto-generated boilerplate" // TODO: put something meaningful here? @@ -84,8 +87,8 @@ object Boilerplate { The block otherwise behaves as a standard interpolated string with regards to variable substitution. */ - object GenApplyBuilders extends Template { - def filename(root: File) = root / "cats" / "syntax" / "ApplyBuilder.scala" + object GenMonoidalBuilders extends Template { + def filename(root: File) = root / "cats" / "syntax" / "MonoidalBuilder.scala" def content(tv: TemplateVals) = { import tv._ @@ -94,7 +97,7 @@ object Boilerplate { val tpesString = synTypes mkString ", " val params = (synVals zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " val next = if (arity + 1 <= maxArity) { - s"def |@|[Z](z: F[Z]) = new ApplyBuilder${arity + 1}(${`a..n`}, z)" + s"def |@|[Z](z: F[Z]) = new MonoidalBuilder${arity + 1}(${`a..n`}, z)" } else { "" } @@ -102,7 +105,7 @@ object Boilerplate { val n = if (arity == 1) { "" } else { arity.toString } val tupled = if (arity != 1) { - s"def tupled(implicit F: Apply[F]): F[(${`A..N`})] = F.tuple$n(${`a..n`})" + s"def tupled(implicit invariant: Invariant[F], monoidal: Monoidal[F]): F[(${`A..N`})] = invariant.tuple$n(${`a..n`})" } else { "" } @@ -111,13 +114,17 @@ object Boilerplate { |package cats |package syntax | - |private[syntax] class ApplyBuilder[F[_]] { - | def |@|[A](a: F[A]) = new ApplyBuilder1(a) + |import cats.functor.{Contravariant, Invariant} | - - private[syntax] class ApplyBuilder$arity[${`A..N`}](${params}) { + |private[syntax] class MonoidalBuilder[F[_]] { + | def |@|[A](a: F[A]) = new MonoidalBuilder1(a) + | + - private[syntax] class MonoidalBuilder$arity[${`A..N`}]($params) { - $next - - def ap[Z](f: F[(${`A..N`}) => Z])(implicit F: Apply[F]): F[Z] = F.ap$n(${`a..n`})(f) - - def map[Z](f: (${`A..N`}) => Z)(implicit F: Apply[F]): F[Z] = F.map$n(${`a..n`})(f) + - def ap[Z](f: F[(${`A..N`}) => Z])(implicit apply: Apply[F], monoidal: Monoidal[F]): F[Z] = apply.ap$n(${`a..n`})(f) + - def map[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F], monoidal: Monoidal[F]): F[Z] = functor.map$n(${`a..n`})(f) + - def contramap[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F], monoidal: Monoidal[F]): F[Z] = contravariant.contramap$n(${`a..n`})(f) + - def imap[Z](f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit invariant: Invariant[F], monoidal: Monoidal[F]): F[Z] = invariant.imap$n(${`a..n`})(f)(g) - $tupled - } |} @@ -132,9 +139,7 @@ object Boilerplate { import tv._ val tpes = synTypes map { tpe => s"F[$tpe]" } - val tpesString = synTypes mkString ", " val fargs = (0 until arity) map { "f" + _ } - val fargsS = fargs mkString ", " val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " val a = arity / 2 @@ -147,15 +152,6 @@ object Boilerplate { def apN(n: Int) = if (n == 1) { "ap" } else { s"ap$n" } def allArgs = (0 until arity) map { "a" + _ } mkString "," - val map = if (arity == 3) { - " ap(f2)(map2(f0, f1)((a, b) => c => f(a, b, c)))" - } else { - block""" - - map2(tuple$a($fArgsA), tuple$b($fArgsB)) { - - case (($argsA), ($argsB)) => f($allArgs) - - } - """ - } val apply = block""" - ${apN(b)}($fArgsB)(${apN(a)}($fArgsA)(map(f)(f => @@ -166,15 +162,80 @@ object Boilerplate { block""" |package cats |trait ApplyArityFunctions[F[_]] { self: Apply[F] => - | def tuple2[A, B](fa: F[A], fb: F[B]): F[(A, B)] = map2(fa, fb)((_, _)) - | - def ap$arity[${`A..N`}, Z]($fparams)(f: F[(${`A..N`}) => Z]):F[Z] = $apply - - def map$arity[${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z):F[Z] = $map - - def tuple$arity[${`A..N`}]($fparams):F[(${`A..N`})] = - - map$arity($fargsS)((${`_.._`})) |} """ } } + object GenFunctorArityFunctions extends Template { + def filename(root: File) = root / "cats" / "FunctorArityFunctions.scala" + override def range = 3 to maxArity + def content(tv: TemplateVals) = { + import tv._ + + val tpes = synTypes map { tpe => s"F[$tpe]" } + val fargs = (0 until arity) map { "f" + _ } + val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " + + val nestedProducts = (0 until (arity - 2)).foldRight(s"F.product(f${arity - 2}, f${arity - 1})")((i, acc) => s"F.product(f$i, $acc)") + val `nested (a..n)` = (0 until (arity - 2)).foldRight(s"(a${arity - 2}, a${arity - 1})")((i, acc) => s"(a$i, $acc)") + + block""" + |package cats + |trait FunctorArityFunctions[F[_]] { self: Functor[F] => + - def map$arity[${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit F: Monoidal[F]): F[Z] = map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } + |} + """ + } + } + + object GenContravariantArityFunctions extends Template { + def filename(root: File) = root / "cats" / "ContravariantArityFunctions.scala" + override def range = 3 to maxArity + def content(tv: TemplateVals) = { + import tv._ + + val tpes = synTypes map { tpe => s"F[$tpe]" } + val fargs = (0 until arity) map { "f" + _ } + val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " + + val nestedProducts = (0 until (arity - 2)).foldRight(s"F.product(f${arity - 2}, f${arity - 1})")((i, acc) => s"F.product(f$i, $acc)") + val `nested (a..n)` = (0 until (arity - 2)).foldRight(s"(a${arity - 2}, a${arity - 1})")((i, acc) => s"(a$i, $acc)") + + block""" + |package cats + |trait ContravariantArityFunctions[F[_]] { self: functor.Contravariant[F] => + - def contramap$arity[${`A..N`}, Z]($fparams)(f: Z => (${`A..N`}))(implicit F: Monoidal[F]):F[Z] = contramap($nestedProducts) { z => val ${`(a..n)`} = f(z); ${`nested (a..n)`} } + |} + """ + } + } + + object GenInvariantArityFunctions extends Template { + def filename(root: File) = root / "cats" / "InvariantArityFunctions.scala" + override def range = 3 to maxArity + def content(tv: TemplateVals) = { + import tv._ + + val tpes = synTypes map { tpe => s"F[$tpe]" } + val fargs = (0 until arity) map { "f" + _ } + val fargsS = fargs mkString ", " + val fparams = (fargs zip tpes) map { case (v,t) => s"$v:$t"} mkString ", " + + val nestedProducts = (0 until (arity - 2)).foldRight(s"F.product(f${arity - 2}, f${arity - 1})")((i, acc) => s"F.product(f$i, $acc)") + val `nested (a..n)` = (0 until (arity - 2)).foldRight(s"(a${arity - 2}, a${arity - 1})")((i, acc) => s"(a$i, $acc)") + val fArgs = (0 until arity).map(i => s"a$i").mkString(",") + + block""" + |package cats + |trait InvariantArityFunctions[F[_]] extends Any { self: functor.Invariant[F] => + - def imap$arity[${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit F: Monoidal[F]):F[Z] = imap($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } { z => val ${`(a..n)`} = g(z); ${`nested (a..n)`} } + - def tuple$arity[${`A..N`}]($fparams)(implicit F: Monoidal[F]):F[(${`A..N`})] = + - imap$arity($fargsS)((${`_.._`}))(identity) + |} + """ + } + } + } diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 0b28502b8ff..103631dda92 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -27,7 +27,7 @@ class StateTTests extends CatsSuite { } }) - test("Apply syntax is usable on State") { + test("Monoidal syntax is usable on State") { val x = add1 *> add1 x.runS(0).run should === (2) }