Skip to content

Commit

Permalink
Generalize ApplyBuilder to Monoidal.
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrf committed Oct 9, 2015
1 parent 5b45c9e commit c102c40
Show file tree
Hide file tree
Showing 13 changed files with 139 additions and 51 deletions.
5 changes: 4 additions & 1 deletion core/src/main/scala/cats/Apply.scala
Expand Up @@ -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
Expand Down Expand Up @@ -45,6 +45,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[_]]
Expand Down
4 changes: 3 additions & 1 deletion core/src/main/scala/cats/Functor.scala
Expand Up @@ -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
trait Functor[F[_]] extends functor.Invariant[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)
Expand Down Expand Up @@ -60,6 +61,7 @@ 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)

}

object Functor {
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/scala/cats/Monoidal.scala
@@ -0,0 +1,15 @@
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)]
}

object Monoidal extends MonoidalArityFunctions
3 changes: 2 additions & 1 deletion core/src/main/scala/cats/functor/Contravariant.scala
Expand Up @@ -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
trait Contravariant[F[_]] extends Invariant[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)

Expand Down
3 changes: 2 additions & 1 deletion core/src/main/scala/cats/functor/Invariant.scala
Expand Up @@ -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
trait Invariant[F[_]] extends Serializable { self =>
def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B]

/**
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Expand Up @@ -3,6 +3,7 @@ package syntax

trait AllSyntax
extends ApplySyntax
with MonoidalSyntax
with BifunctorSyntax
with CoflatMapSyntax
with ComonadSyntax
Expand Down
22 changes: 4 additions & 18 deletions core/src/main/scala/cats/syntax/apply.scala
Expand Up @@ -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)
}
28 changes: 28 additions & 0 deletions 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 }

}
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/package.scala
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions docs/src/main/tut/apply.md
Expand Up @@ -118,7 +118,7 @@ In order to use it, first import `cats.syntax.all._` or `cats.syntax.apply._`.
Here we see that the following two functions, `f1` and `f2`, are equivalent:

```tut
import cats.syntax.apply._
import cats.syntax.monoidal._
def f1(a: Option[Int], b: Option[Int], c: Option[Int]) =
(a |@| b |@| c) map { _ * _ * _ }
Expand All @@ -133,7 +133,7 @@ f2(Some(1), Some(2), Some(3))
All instances created by `|@|` have `map`, `ap`, and `tupled` methods of the appropriate arity:

```tut
import cats.syntax.apply._
import cats.syntax.monoidal._
val option2 = Option(1) |@| Option(2)
val option3 = option2 |@| Option.empty[Int]
Expand Down
16 changes: 16 additions & 0 deletions 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 }

}
86 changes: 60 additions & 26 deletions project/Boilerplate.scala
Expand Up @@ -25,7 +25,8 @@ object Boilerplate {


val templates: Seq[Template] = Seq(
GenApplyBuilders,
GenMonoidalBuilders,
GenMonoidalArityFunctions,
GenApplyArityFunctions
)

Expand Down Expand Up @@ -84,8 +85,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._
Expand All @@ -94,15 +95,27 @@ 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 {
""
}

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(${`a..n`})(f)"
else s"def map[Z](f: (${`A..N`}) => Z)(implicit functor: Functor[F], monoidal: Monoidal[F]): F[Z] = Monoidal.map$n(${`a..n`})(f)"

val contramap =
if (arity == 1) s"def contramap[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F]): F[Z] = contravariant.contramap(${`a..n`})(f)"
else s"def contramap[Z](f: Z => (${`A..N`}))(implicit contravariant: Contravariant[F], monoidal: Monoidal[F]): F[Z] = Monoidal.contramap$n(${`a..n`})(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(${`a..n`})(f)(g)"
else s"def imap[Z](f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit invariant: Invariant[F], monoidal: Monoidal[F]): F[Z] = Monoidal.imap$n(${`a..n`})(f)(g)"

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`})] = Monoidal.tuple$n(${`a..n`})"
} else {
""
}
Expand All @@ -111,13 +124,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]): F[Z] = apply.ap$n(${`a..n`})(f)
- $map
- $contramap
- $imap
- $tupled
- }
|}
Expand All @@ -132,9 +149,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
Expand All @@ -147,15 +162,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 =>
Expand All @@ -166,15 +172,43 @@ 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 tuple2[A, B](f1: F[A], f2: F[B]): F[(A, B)] = Monoidal.tuple2(f1, f2)(self, self)
- 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)((${`_.._`}))
- def map$arity[${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z): F[Z] = Monoidal.map$arity($fparams)(f)(self, self)
- def tuple$arity[${`A..N`}, Z]($fparams): F[(${`A..N`})] = Monoidal.tuple$arity($fparams)(self, self)
|}
"""
}
}

object GenMonoidalArityFunctions extends Template {
def filename(root: File) = root / "cats" / "MonoidalArityFunctions.scala"
override def range = 2 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 fargsS = fargs mkString ", "

val nestedProducts = (0 until (arity - 2)).foldRight(s"monoidal.product(f${arity - 2}, f${arity - 1})")((i, acc) => s"monoidal.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 MonoidalArityFunctions {
- def map$arity[F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(implicit monoidal: Monoidal[F], functor: Functor[F]): F[Z] =
- functor.map($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) }
- def contramap$arity[F[_], ${`A..N`}, Z]($fparams)(f: Z => (${`A..N`}))(implicit monoidal: Monoidal[F], contravariant: functor.Contravariant[F]):F[Z] =
- contravariant.contramap($nestedProducts) { z => val ${`(a..n)`} = f(z); ${`nested (a..n)`} }
- def imap$arity[F[_], ${`A..N`}, Z]($fparams)(f: (${`A..N`}) => Z)(g: Z => (${`A..N`}))(implicit monoidal: Monoidal[F], invariant: functor.Invariant[F]):F[Z] =
- invariant.imap($nestedProducts) { case ${`nested (a..n)`} => f(${`a..n`}) } { z => val ${`(a..n)`} = g(z); ${`nested (a..n)`} }
- def tuple$arity[F[_], ${`A..N`}]($fparams)(implicit monoidal: Monoidal[F], invariant: functor.Invariant[F]):F[(${`A..N`})] =
- imap$arity($fargsS)((${`_.._`}))(identity)
|}
"""
}
}

}
2 changes: 1 addition & 1 deletion state/src/test/scala/cats/state/StateTTests.scala
Expand Up @@ -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)
}
Expand Down

0 comments on commit c102c40

Please sign in to comment.