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 15, 2015
1 parent cb804ce commit 00e5ab4
Show file tree
Hide file tree
Showing 21 changed files with 223 additions and 70 deletions.
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[_]] {
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 @@ -76,6 +76,12 @@ 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 @@ -89,6 +95,9 @@ 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 @@ 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 @@ abstract class AppFuncInstances {

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 @@ -147,6 +147,12 @@ 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 @@ -155,6 +161,9 @@ 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 @@ sealed abstract class ProdInstance4 {
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 @@ -111,15 +111,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 @@ -196,8 +206,11 @@ sealed abstract class ValidatedInstances extends ValidatedInstances1 {
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
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
10 changes: 8 additions & 2 deletions docs/src/main/tut/apply.md
Expand Up @@ -25,13 +25,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 @@ -118,7 +124,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 +139,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
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
2 changes: 2 additions & 0 deletions docs/src/main/tut/validated.md
Expand Up @@ -197,6 +197,8 @@ implicit def validatedApplicative[E : Semigroup]: Applicative[Validated[E, ?]] =
}
def pure[A](x: A): Validated[E, A] = Validated.valid(x)
def map[A, B](fa: Validated[E, A])(f: A => B): Validated[E, B] = fa.map(f)
def product[A, B](fa: Validated[E, A], fb: Validated[E, B]): Validated[E, (A, B)] = ap(fb)(fa.map(a => b => (a, b)))
}
```

Expand Down

0 comments on commit 00e5ab4

Please sign in to comment.