Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize ApplyBuilder to Monoidal structures #555

Merged
merged 11 commits into from Dec 11, 2015
2 changes: 0 additions & 2 deletions core/src/main/scala/cats/Applicative.scala
Expand Up @@ -28,8 +28,6 @@ import simulacrum.typeclass
*/
def pureEval[A](x: Eval[A]): F[A] = pure(x.value)

override def map[A, B](fa: F[A])(f: A => B): F[B] = ap(fa)(pure(f))

/**
* Two sequentially dependent Applicatives can be composed.
*
Expand Down
15 changes: 10 additions & 5 deletions core/src/main/scala/cats/Apply.scala
Expand Up @@ -8,27 +8,27 @@ 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
* function to the value.
*/
def ap[A, B](fa: F[A])(f: F[A => B]): F[B]
def ap[A, B](fa: F[A])(ff: F[A => B]): F[B]

/**
* ap2 is a binary version of ap, defined in terms of ap.
*/
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))))
def ap2[A, B, Z](fa: F[A], fb: F[B])(ff: F[(A, B) => Z]): F[Z] =
map(product(fa, product(fb, ff))) { case (a, (b, f)) => 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)))
map(product(fa, fb)) { case (a, b) => f(a, b) }

/**
* Two sequentially dependent Applys can be composed.
Expand All @@ -45,6 +45,7 @@ trait Apply[F[_]] extends Functor[F] with ApplyArityFunctions[F] { self =>
def F: Apply[F] = self
def G: Apply[G] = GG
}

}

trait CompositeApply[F[_], G[_]]
Expand All @@ -54,4 +55,8 @@ trait CompositeApply[F[_], G[_]]

def ap[A, B](fa: F[G[A]])(f: F[G[A => B]]): F[G[B]] =
F.ap(fa)(F.map(f)(gab => G.ap(_)(gab)))

def product[A, B](fa: F[G[A]], fb: F[G[B]]): F[G[(A, B)]] =
F.map2(fa, fb)(G.product)

}
3 changes: 3 additions & 0 deletions core/src/main/scala/cats/FlatMap.scala
Expand Up @@ -29,6 +29,9 @@ import simulacrum.typeclass
override def ap[A, B](fa: F[A])(ff: F[A => B]): F[B] =
flatMap(ff)(f => map(fa)(f))

override def product[A, B](fa: F[A], fb: F[B]): F[(A, B)] =
flatMap(fa)(a => map(fb)(b => (a, b)))

/**
* Pair `A` with the result of function application.
*/
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[_]] {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any opinions on renaming this to Product? Edit: forgot that's taken already. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not "Cartesian" because Monoidal sounds different ?

def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}

object Monoidal extends MonoidalArityFunctions
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/data/Const.scala
Expand Up @@ -88,6 +88,12 @@ private[data] sealed abstract class ConstInstances0 extends ConstInstances1 {

def ap[A, B](fa: Const[C, A])(f: Const[C, A => B]): Const[C, B] =
f.retag[B] combine fa.retag[B]

def map[A, B](fa: Const[C, A])(f: A => B): Const[C, B] =
fa.retag[B]

def product[A, B](fa: Const[C, A], fb: Const[C, B]): Const[C, (A, B)] =
fa.retag[(A, B)] combine fb.retag[(A, B)]
}
}

Expand All @@ -101,6 +107,9 @@ private[data] sealed abstract class ConstInstances1 {
def ap[A, B](fa: Const[C, A])(f: Const[C, A => B]): Const[C, B] =
fa.retag[B] combine f.retag[B]

def product[A, B](fa: Const[C, A], fb: Const[C, B]): Const[C, (A, B)] =
fa.retag[(A, B)] combine fb.retag[(A, B)]

def map[A, B](fa: Const[C, A])(f: A => B): Const[C, B] =
fa.retag[B]
}
Expand Down
16 changes: 10 additions & 6 deletions core/src/main/scala/cats/data/Func.scala
Expand Up @@ -54,19 +54,21 @@ private[data] abstract class FuncInstances1 {

sealed trait FuncFunctor[F[_], C] extends Functor[Lambda[X => Func[F, C, X]]] {
def F: Functor[F]
override def map[A, B](fa: Func[F, C, A])(f: A => B): Func[F, C, B] =
def map[A, B](fa: Func[F, C, A])(f: A => B): Func[F, C, B] =
fa.map(f)(F)
}

sealed trait FuncApply[F[_], C] extends Apply[Lambda[X => Func[F, C, X]]] with FuncFunctor[F, C] {
def F: Apply[F]
override def ap[A, B](fa: Func[F, C, A])(f: Func[F, C, A => B]): Func[F, C, B] =
def ap[A, B](fa: Func[F, C, A])(f: Func[F, C, A => B]): Func[F, C, B] =
Func.func(c => F.ap(fa.run(c))(f.run(c)))
def product[A, B](fa: Func[F, C, A], fb: Func[F, C, B]): Func[F, C, (A, B)] =
Func.func(c => F.product(fa.run(c), fb.run(c)))
}

sealed trait FuncApplicative[F[_], C] extends Applicative[Lambda[X => Func[F, C, X]]] with FuncApply[F, C] {
def F: Applicative[F]
override def pure[A](a: A): Func[F, C, A] =
def pure[A](a: A): Func[F, C, A] =
Func.func(c => F.pure(a))
}

Expand Down Expand Up @@ -119,10 +121,12 @@ private[data] abstract class AppFuncInstances {

private[data] sealed trait AppFuncApplicative[F[_], C] extends Applicative[Lambda[X => AppFunc[F, C, X]]] {
def F: Applicative[F]
override def map[A, B](fa: AppFunc[F, C, A])(f: A => B): AppFunc[F, C, B] =
def map[A, B](fa: AppFunc[F, C, A])(f: A => B): AppFunc[F, C, B] =
fa.map(f)
override def ap[A, B](fa: AppFunc[F, C, A])(f: AppFunc[F, C, A => B]): AppFunc[F, C, B] =
def ap[A, B](fa: AppFunc[F, C, A])(f: AppFunc[F, C, A => B]): AppFunc[F, C, B] =
Func.appFunc[F, C, B](c => F.ap(fa.run(c))(f.run(c)))(F)
override def pure[A](a: A): AppFunc[F, C, A] =
def product[A, B](fa: AppFunc[F, C, A], fb: AppFunc[F, C, B]): AppFunc[F, C, (A, B)] =
Func.appFunc[F, C, (A, B)](c => F.product(fa.run(c), fb.run(c)))(F)
def pure[A](a: A): AppFunc[F, C, A] =
Func.appFunc[F, C, A](c => F.pure(a))(F)
}
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/data/Kleisli.scala
Expand Up @@ -148,6 +148,12 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2

def ap[B, C](fa: Kleisli[F, A, B])(f: Kleisli[F, A, B => C]): Kleisli[F, A, C] =
fa(f)

def map[B, C](fb: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] =
fb.map(f)

def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] =
Kleisli.function(a => Applicative[F].product(fb.run(a), fc.run(a)))
}
}

Expand All @@ -156,6 +162,9 @@ private[data] sealed abstract class KleisliInstances2 extends KleisliInstances3
def ap[B, C](fa: Kleisli[F, A, B])(f: Kleisli[F, A, B => C]): Kleisli[F, A, C] =
fa(f)

def product[B, C](fb: Kleisli[F, A, B], fc: Kleisli[F, A, C]): Kleisli[F, A, (B, C)] =
Kleisli.function(a => Apply[F].product(fb.run(a), fc.run(a)))

def map[B, C](fa: Kleisli[F, A, B])(f: B => C): Kleisli[F, A, C] =
fa.map(f)
}
Expand Down
8 changes: 5 additions & 3 deletions core/src/main/scala/cats/data/Prod.scala
Expand Up @@ -71,20 +71,22 @@ private[data] sealed abstract class ProdInstances4 {
sealed trait ProdFunctor[F[_], G[_]] extends Functor[Lambda[X => Prod[F, G, X]]] {
def F: Functor[F]
def G: Functor[G]
override def map[A, B](fa: Prod[F, G, A])(f: A => B): Prod[F, G, B] = Prod(F.map(fa.first)(f), G.map(fa.second)(f))
def map[A, B](fa: Prod[F, G, A])(f: A => B): Prod[F, G, B] = Prod(F.map(fa.first)(f), G.map(fa.second)(f))
}

sealed trait ProdApply[F[_], G[_]] extends Apply[Lambda[X => Prod[F, G, X]]] with ProdFunctor[F, G] {
def F: Apply[F]
def G: Apply[G]
override def ap[A, B](fa: Prod[F, G, A])(f: Prod[F, G, A => B]): Prod[F, G, B] =
def ap[A, B](fa: Prod[F, G, A])(f: Prod[F, G, A => B]): Prod[F, G, B] =
Prod(F.ap(fa.first)(f.first), G.ap(fa.second)(f.second))
def product[A, B](fa: Prod[F, G, A], fb: Prod[F, G, B]): Prod[F, G, (A, B)] =
Prod(F.product(fa.first, fb.first), G.product(fa.second, fb.second))
}

sealed trait ProdApplicative[F[_], G[_]] extends Applicative[Lambda[X => Prod[F, G, X]]] with ProdApply[F, G] {
def F: Applicative[F]
def G: Applicative[G]
override def pure[A](a: A): Prod[F, G, A] = Prod(F.pure(a), G.pure(a))
def pure[A](a: A): Prod[F, G, A] = Prod(F.pure(a), G.pure(a))
}

sealed trait ProdSemigroupK[F[_], G[_]] extends SemigroupK[Lambda[X => Prod[F, G, X]]] {
Expand Down
19 changes: 16 additions & 3 deletions core/src/main/scala/cats/data/Validated.scala
Expand Up @@ -105,15 +105,25 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable {
b => that.fold(_ => false, AA.eqv(b, _))
)


/**
* From Apply:
* if both the function and this value are Valid, apply the function
*/
def ap[EE >: E, B](f: Validated[EE, A => B])(implicit EE: Semigroup[EE]): Validated[EE,B] =
(this, f) match {
case (Valid(a), Valid(f)) => Valid(f(a))
case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e2,e1))
case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e2, e1))
case (e@Invalid(_), _) => e
case (_, e@Invalid(_)) => e
}

/**
* From Product
*/
def product[EE >: E, B](fb: Validated[EE, B])(implicit EE: Semigroup[EE]): Validated[EE, (A, B)] =
(this, fb) match {
case (Valid(a), Valid(b)) => Valid((a, b))
case (Invalid(e1), Invalid(e2)) => Invalid(EE.combine(e1, e2))
case (e @ Invalid(_), _) => e
case (_, e @ Invalid(_)) => e
}
Expand Down Expand Up @@ -237,8 +247,11 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance
override def map[A, B](fa: Validated[E,A])(f: A => B): Validated[E, B] =
fa.map(f)

override def ap[A,B](fa: Validated[E,A])(f: Validated[E,A=>B]): Validated[E, B] =
def ap[A,B](fa: Validated[E,A])(f: Validated[E,A=>B]): Validated[E, B] =
fa.ap(f)(E)

def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] =
fa.product(fb)(E)
}
}

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/data/WriterT.scala
Expand Up @@ -157,6 +157,8 @@ private[data] sealed trait WriterTApply[F[_], L] extends WriterTFunctor[F, L] wi

def ap[A, B](fa: WriterT[F, L, A])(f: WriterT[F, L, A => B]): WriterT[F, L, B] =
fa ap f
def product[A, B](fa: WriterT[F, L, A], fb: WriterT[F, L, B]): WriterT[F, L, (A, B)] =
WriterT(F0.map(F0.product(fa.run, fb.run)) { case ((l1, a), (l2, b)) => (L0.combine(l1, l2), (a, b)) })
}

private[data] sealed trait WriterTFlatMap[F[_], L] extends WriterTApply[F, L] with FlatMap[WriterT[F, L, ?]] {
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/data/XorT.scala
Expand Up @@ -120,7 +120,7 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) {
* {{{
* scala> import cats.std.option._
* scala> import cats.std.list._
* scala> import cats.syntax.apply._
* scala> import cats.syntax.monoidal._
* scala> type Error = String
* scala> val v1: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 1"))
* scala> val v2: Validated[NonEmptyList[Error], Int] = Validated.Invalid(NonEmptyList("error 2"))
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
8 changes: 7 additions & 1 deletion docs/src/main/tut/apply.md
Expand Up @@ -26,13 +26,19 @@ implicit val optionApply: Apply[Option] = new Apply[Option] {
fa.flatMap (a => f.map (ff => ff(a)))

def map[A,B](fa: Option[A])(f: A => B): Option[B] = fa map f

def product[A, B](fa: Option[A], fb: Option[B]): Option[(A, B)] =
fa.flatMap(a => fb.map(b => (a, b)))
}

implicit val listApply: Apply[List] = new Apply[List] {
def ap[A, B](fa: List[A])(f: List[A => B]): List[B] =
fa.flatMap (a => f.map (ff => ff(a)))

def map[A,B](fa: List[A])(f: A => B): List[B] = fa map f

def product[A, B](fa: List[A], fb: List[B]): List[(A, B)] =
fa.zip(fb)
}
```

Expand Down Expand Up @@ -119,7 +125,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 Down
12 changes: 11 additions & 1 deletion docs/src/main/tut/const.md
Expand Up @@ -219,13 +219,17 @@ implicit def constApplicative[Z]: Applicative[Const[Z, ?]] =
def pure[A](a: A): Const[Z, A] = ???

def ap[A, B](fa: Const[Z, A])(f: Const[Z, A => B]): Const[Z, B] = ???

def map[A, B](fa: Const[Z, A])(f: A => B): Const[Z, B] = ???

def product[A, B](fa: Const[Z, A],fb: Const[Z, B]): Const[Z, (A, B)] = ???
}
```

Recall that `Const[Z, A]` means we have a `Z` value in hand, and don't really care about the `A` type parameter.
Therefore we can more or less treat the type `Const[Z, A]` as just `Z`.

In both functions we have a problem. In `pure`, we have an `A` value, but want to return a `Z` value. We have
In functions `pure` and `ap` we have a problem. In `pure`, we have an `A` value, but want to return a `Z` value. We have
no function `A => Z`, so our only option is to completely ignore the `A` value. But we still don't have a `Z`! Let's
put that aside for now, but still keep it in the back of our minds.

Expand All @@ -242,6 +246,12 @@ implicit def constApplicative[Z : Monoid]: Applicative[Const[Z, ?]] =

def ap[A, B](fa: Const[Z, A])(f: Const[Z, A => B]): Const[Z, B] =
Const(Monoid[Z].combine(fa.getConst, f.getConst))

def map[A, B](fa: Const[Z, A])(f: A => B): Const[Z, B] =
Const(fa.getConst)

def product[A, B](fa: Const[Z, A],fb: Const[Z, B]): Const[Z, (A, B)] =
Const(Monoid[Z].combine(fa.getConst, fb.getConst))
}
```

Expand Down