Skip to content

Commit

Permalink
Add Semigroupal for Monoidal hierarchy
Browse files Browse the repository at this point in the history
- Make (new) Semigroupal extend Functor
- Add Monoidal and associated tests
- Applicative extends Monoidal
  • Loading branch information
adelbertc committed Jan 10, 2016
1 parent 0529841 commit ed09428
Show file tree
Hide file tree
Showing 56 changed files with 260 additions and 179 deletions.
4 changes: 2 additions & 2 deletions core/src/main/scala/cats/Applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import simulacrum.typeclass
*
* Must obey the laws defined in cats.laws.ApplicativeLaws.
*/
@typeclass trait Applicative[F[_]] extends Apply[F] { self =>
@typeclass trait Applicative[F[_]] extends Apply[F] with Monoidal[F] { self =>

/**
* `pure` lifts any value into the Applicative Functor.
Expand All @@ -26,7 +26,7 @@ import simulacrum.typeclass
*
* This variant supports optional laziness.
*/
def pureEval[A](x: Eval[A]): F[A] = pure(x.value)
override def pureEval[A](x: Eval[A]): F[A] = pure(x.value)

/**
* Two sequentially dependent Applicatives can be composed.
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/Apply.scala
Original file line number Diff line number Diff line change
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 Monoidal[F] with ApplyArityFunctions[F] { self =>
trait Apply[F[_]] extends Functor[F] with Semigroupal[F] with ApplyArityFunctions[F] { self =>

/**
* Given a value and a function in the Apply context, applies the
Expand Down
16 changes: 12 additions & 4 deletions core/src/main/scala/cats/Monoidal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ import simulacrum.typeclass
*
* 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)]
}
@typeclass trait Monoidal[F[_]] extends Semigroupal[F] {
/**
* `pure` lifts any value into the lax monoidal functor.
*/
def pure[A](x: A): F[A]

object Monoidal extends MonoidalArityFunctions
/**
* `pureEval` lifts any value into the lax monoidal functor.
*
* This variant supports optional laziness.
*/
def pureEval[A](x: Eval[A]): F[A] = pure(x.value)
}
16 changes: 16 additions & 0 deletions core/src/main/scala/cats/Semigroupal.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cats

import simulacrum.typeclass

/**
* [[Semigroupal]] generalizes [[Functor]] by allowing functions of any
* arity to be applied to an effectful value. It encodes this by pairing
* together two effectful values (which can then be extended to be three
* or more) within a context, which can then be mapped over via the [[Functor]]
* instance.
*/
@typeclass trait Semigroupal[F[_]] extends Functor[F] {
def product[A, B](fa: F[A], fb: F[B]): F[(A, B)]
}

object Semigroupal extends SemigroupalArityFunctions
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/data/XorT.scala
Original file line number Diff line number Diff line change
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.monoidal._
* scala> import cats.syntax.semigroupal._
* 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
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package syntax

trait AllSyntax
extends ApplySyntax
with MonoidalSyntax
with BifunctorSyntax
with CoflatMapSyntax
with ComonadSyntax
Expand All @@ -25,6 +24,7 @@ trait AllSyntax
with ProfunctorSyntax
with ReducibleSyntax
with SemigroupSyntax
with SemigroupalSyntax
with SemigroupKSyntax
with Show.ToShowOps
with SplitSyntax
Expand Down
18 changes: 9 additions & 9 deletions core/src/main/scala/cats/syntax/monoidal.scala
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
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] {
trait SemigroupalSyntax1 {
implicit def monoidalSyntaxU[FA](fa: FA)(implicit U: Unapply[Semigroupal, FA]): SemigroupalOps[U.M, U.A] =
new SemigroupalOps[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] {
trait SemigroupalSyntax extends SemigroupalSyntax1 {
implicit def monoidalSyntax[F[_], A](fa: F[A])(implicit F: Semigroupal[F]): SemigroupalOps[F, A] =
new SemigroupalOps[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
abstract class SemigroupalOps[F[_], A] extends Semigroupal.Ops[F, A] {
def |@|[B](fb: F[B]): SemigroupalBuilder[F]#SemigroupalBuilder2[A, B] =
new SemigroupalBuilder[F] |@| self |@| fb

def *>[B](fb: F[B])(implicit F: Functor[F]): F[B] = F.map(typeClassInstance.product(self, fb)) { case (a, b) => b }

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ 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 All @@ -23,6 +22,7 @@ package object syntax {
object partialOrder extends PartialOrderSyntax
object profunctor extends ProfunctorSyntax
object semigroup extends SemigroupSyntax
object semigroupal extends SemigroupalSyntax
object semigroupk extends SemigroupKSyntax
object show extends Show.ToShowOps
object split extends SplitSyntax
Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/tut/apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,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.monoidal._
import cats.syntax.semigroupal._
def f1(a: Option[Int], b: Option[Int], c: Option[Int]) =
(a |@| b |@| c) map { _ * _ * _ }
Expand Down
2 changes: 1 addition & 1 deletion docs/src/main/tut/freeapplicative.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Because a `FreeApplicative` only supports the operations of `Applicative`, we do
of a for-comprehension. We can however still use `Applicative` syntax provided by Cats.

```tut:silent
import cats.syntax.monoidal._
import cats.syntax.semigroupal._
val prog: Validation[Boolean] = (size(5) |@| hasNumber).map { case (l, r) => l && r}
```
Expand Down
2 changes: 1 addition & 1 deletion laws/src/main/scala/cats/laws/ApplicativeLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import cats.syntax.functor._
/**
* Laws that must be obeyed by any `Applicative`.
*/
trait ApplicativeLaws[F[_]] extends ApplyLaws[F] {
trait ApplicativeLaws[F[_]] extends ApplyLaws[F] with MonoidalLaws[F] {
implicit override def F: Applicative[F]

def applicativeIdentity[A](fa: F[A]): IsEq[F[A]] =
Expand Down
2 changes: 1 addition & 1 deletion laws/src/main/scala/cats/laws/ApplyLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import cats.syntax.functor._
/**
* Laws that must be obeyed by any `Apply`.
*/
trait ApplyLaws[F[_]] extends FunctorLaws[F] with MonoidalLaws[F] {
trait ApplyLaws[F[_]] extends FunctorLaws[F] with SemigroupalLaws[F] {
implicit override def F: Apply[F]

def applyComposition[A, B, C](fa: F[A], fab: F[A => B], fbc: F[B => C]): IsEq[F[C]] = {
Expand Down
10 changes: 6 additions & 4 deletions laws/src/main/scala/cats/laws/MonoidalLaws.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package cats
package laws

trait MonoidalLaws[F[_]] {
trait MonoidalLaws[F[_]] extends SemigroupalLaws[F] {

implicit def F: Monoidal[F]

def monoidalAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) =
(F.product(fa, F.product(fb, fc)), F.product(F.product(fa, fb), fc))
def leftIdentity[A](fa: F[A]): IsEq[F[A]] =
F.map(F.product(F.pure(()), fa))(_._2) <-> fa

def rightIdentity[A](fa: F[A]): IsEq[F[A]] =
F.map(F.product(fa, F.pure(())))(_._1) <-> fa
}

object MonoidalLaws {

def apply[F[_]](implicit ev: Monoidal[F]): MonoidalLaws[F] =
new MonoidalLaws[F] { val F = ev }

}
}
18 changes: 18 additions & 0 deletions laws/src/main/scala/cats/laws/SemigroupalLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cats
package laws

trait SemigroupalLaws[F[_]] {

implicit def F: Semigroupal[F]

def semigroupalAssociativity[A, B, C](fa: F[A], fb: F[B], fc: F[C]): (F[(A, (B, C))], F[((A, B), C)]) =
(F.product(fa, F.product(fb, fc)), F.product(F.product(fa, fb), fc))

}

object SemigroupalLaws {

def apply[F[_]](implicit ev: Semigroupal[F]): SemigroupalLaws[F] =
new SemigroupalLaws[F] { val F = ev }

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._
Expand Down
22 changes: 13 additions & 9 deletions laws/src/main/scala/cats/laws/discipline/ApplicativeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._

trait ApplicativeTests[F[_]] extends ApplyTests[F] {
trait ApplicativeTests[F[_]] extends ApplyTests[F] with MonoidalTests[F] {
def laws: ApplicativeLaws[F]

def applicative[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit
Expand All @@ -22,13 +22,17 @@ trait ApplicativeTests[F[_]] extends ApplyTests[F] {
EqFABC: Eq[F[(A, B, C)]],
iso: Isomorphisms[F]
): RuleSet = {
new DefaultRuleSet(
name = "applicative",
parent = Some(apply[A, B, C]),
"applicative identity" -> forAll(laws.applicativeIdentity[A] _),
"applicative homomorphism" -> forAll(laws.applicativeHomomorphism[A, B] _),
"applicative interchange" -> forAll(laws.applicativeInterchange[A, B] _),
"applicative map" -> forAll(laws.applicativeMap[A, B] _))
new RuleSet {
def name: String = "applicative"
def bases: Seq[(String, RuleSet)] = Nil
def parents: Seq[RuleSet] = Seq(apply[A, B, C], monoidal[A, B, C])
def props: Seq[(String, Prop)] = Seq(
"applicative identity" -> forAll(laws.applicativeIdentity[A] _),
"applicative homomorphism" -> forAll(laws.applicativeHomomorphism[A, B] _),
"applicative interchange" -> forAll(laws.applicativeInterchange[A, B] _),
"applicative map" -> forAll(laws.applicativeMap[A, B] _)
)
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions laws/src/main/scala/cats/laws/discipline/ApplyTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._

trait ApplyTests[F[_]] extends FunctorTests[F] with MonoidalTests[F] {
trait ApplyTests[F[_]] extends FunctorTests[F] with SemigroupalTests[F] {
def laws: ApplyLaws[F]

def apply[A: Arbitrary, B: Arbitrary, C: Arbitrary](implicit
Expand All @@ -22,7 +22,7 @@ trait ApplyTests[F[_]] extends FunctorTests[F] with MonoidalTests[F] {
iso: Isomorphisms[F]
): RuleSet = new RuleSet {
val name = "apply"
val parents = Seq(functor[A, B, C], monoidal[A, B, C])
val parents = Seq(functor[A, B, C], semigroupal[A, B, C])
val bases = Seq.empty
val props = Seq("apply composition" -> forAll(laws.applyComposition[A, B, C] _))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package laws
package discipline

import cats.data.{ Xor, XorT }
import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq.unitEq
import org.scalacheck.{Arbitrary, Prop}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.{Arbitrary, Prop}
import org.scalacheck.Prop.forAll

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import eq.unitEq
import org.scalacheck.{Arbitrary, Prop}
import org.scalacheck.Prop.forAll
Expand Down
2 changes: 1 addition & 1 deletion laws/src/main/scala/cats/laws/discipline/MonadTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package laws
package discipline

import cats.laws.discipline.MonoidalTests.Isomorphisms
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import Prop._
Expand Down
Loading

0 comments on commit ed09428

Please sign in to comment.