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 57824dd
Show file tree
Hide file tree
Showing 53 changed files with 257 additions and 176 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/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 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
29 changes: 8 additions & 21 deletions laws/src/main/scala/cats/laws/discipline/MonoidalTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,40 +7,27 @@ import org.scalacheck.Prop
import Prop._
import org.typelevel.discipline.Laws

trait MonoidalTests[F[_]] extends Laws {
trait MonoidalTests[F[_]] extends SemigroupalTests[F] {
def laws: MonoidalLaws[F]

def monoidal[A : Arbitrary, B : Arbitrary, C : Arbitrary](implicit
iso: MonoidalTests.Isomorphisms[F],
iso: SemigroupalTests.Isomorphisms[F],
ArbFA: Arbitrary[F[A]],
ArbFB: Arbitrary[F[B]],
ArbFC: Arbitrary[F[C]],
EqFA: Eq[F[A]],
EqFABC: Eq[F[(A, B, C)]]
): RuleSet = {
new DefaultRuleSet(
name = "monoidal",
parent = None,
"monoidal associativity" -> forAll((fa: F[A], fb: F[B], fc: F[C]) => iso.associativity(laws.monoidalAssociativity(fa, fb, fc)))
parent = Some(semigroupal[A, B, C]),
"monoidal left identity" -> forAll(laws.leftIdentity[A] _),
"monoidal right identity" -> forAll(laws.rightIdentity[A] _)
)
}
}

object MonoidalTests {
def apply[F[_] : Monoidal](implicit ev: Isomorphisms[F]): MonoidalTests[F] =
def apply[F[_] : Monoidal](implicit ev: SemigroupalTests.Isomorphisms[F]): MonoidalTests[F] =
new MonoidalTests[F] { val laws: MonoidalLaws[F] = MonoidalLaws[F] }

trait Isomorphisms[F[_]] {
def associativity[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]): Prop
}

object Isomorphisms {
import algebra.laws._
implicit def invariant[F[_]](implicit F: functor.Invariant[F]): Isomorphisms[F] =
new Isomorphisms[F] {
def associativity[A, B, C](fs: (F[(A, (B, C))], F[((A, B), C)]))(implicit EqFABC: Eq[F[(A, B, C)]]) =
F.imap(fs._1) { case (a, (b, c)) => (a, b, c) } { case (a, b, c) => (a, (b, c)) } ?==
F.imap(fs._2) { case ((a, b), c) => (a, b, c) } { case (a, b, c) => ((a, b), c) }
}
}

}
}
Loading

0 comments on commit 57824dd

Please sign in to comment.