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

Add SemigroupK and MonoidK laws (address #196). #198

Merged
merged 15 commits into from
Feb 20, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions laws/src/main/scala/cats/laws/MonoidKLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package cats
package laws

/**
* Laws that must be obeyed by any [[cats.MonoidK]].
*/
trait MonoidKLaws[F[_]] extends SemigroupKLaws[F] {
override implicit def F: MonoidK[F]

def leftIdentity[A](a: F[A]): IsEq[F[A]] =
F.combine(F.empty, a) <-> a

def rightIdentity[A](a: F[A]): IsEq[F[A]] =
F.combine(a, F.empty) <-> a
}

object MonoidKLaws {
def apply[F[_]](implicit ev: MonoidK[F]): MonoidKLaws[F] =
new MonoidKLaws[F] { def F = ev }
}
17 changes: 17 additions & 0 deletions laws/src/main/scala/cats/laws/SemigroupKLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cats
package laws

/**
* Laws that must be obeyed by any [[cats.SemigroupK]].
*/
trait SemigroupKLaws[F[_]] {
implicit def F: SemigroupK[F]

def associative[A](a: F[A], b: F[A], c: F[A]): IsEq[F[A]] =
F.combine(F.combine(a, b), c) <-> F.combine(a, F.combine(b, c))
}

object SemigroupKLaws {
def apply[F[_]](implicit ev: SemigroupK[F]): SemigroupKLaws[F] =
new SemigroupKLaws[F] { def F = ev }
}
6 changes: 6 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,10 @@ object ArbitraryK {
def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Future[A]] =
Arbitrary(A.arbitrary.map(Future.successful))
}

implicit val stream: ArbitraryK[Stream] =
new ArbitraryK[Stream] { def synthesize[A: Arbitrary]: Arbitrary[Stream[A]] = implicitly }

implicit val vector: ArbitraryK[Vector] =
new ArbitraryK[Vector] { def synthesize[A: Arbitrary]: Arbitrary[Vector[A]] = implicitly }
}
32 changes: 32 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/MonoidKTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cats
package laws
package discipline

import org.scalacheck.Prop._
import org.scalacheck.Arbitrary

trait MonoidKTests[F[_]] extends SemigroupKTests[F] {
def laws: MonoidKLaws[F]

def monoidK[A: Arbitrary](implicit
ArbF: ArbitraryK[F],
EqFA: Eq[F[A]]
): RuleSet = {
implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A]

new RuleSet {
val name = "monoidK"
val bases = Nil
val parents = Seq(semigroupK[A])
val props = Seq(
"left identity" -> forAll(laws.leftIdentity[A] _),
"right identity" -> forAll(laws.rightIdentity[A] _)
)
}
}
}

object MonoidKTests {
def apply[F[_] : MonoidK]: MonoidKTests[F] =
new MonoidKTests[F] { def laws = MonoidKLaws[F] }
}
32 changes: 32 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/SemigroupKTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cats
package laws
package discipline

import org.scalacheck.Prop._
import org.scalacheck.Arbitrary
import org.typelevel.discipline.Laws

trait SemigroupKTests[F[_]] extends Laws {
def laws: SemigroupKLaws[F]

def semigroupK[A: Arbitrary](implicit
ArbF: ArbitraryK[F],
EqFA: Eq[F[A]]
): RuleSet = {
implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A]

new RuleSet {
val name = "semigroupK"
val bases = Nil
val parents = Nil
val props = Seq(
"associative" -> forAll(laws.associative[A] _)
)
}
}
}

object SemigroupKTests {
def apply[F[_]: SemigroupK]: SemigroupKTests[F] =
new SemigroupKTests[F] { def laws = SemigroupKLaws[F] }
}
22 changes: 21 additions & 1 deletion std/src/main/scala/cats/std/stream.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats
package std

import scala.annotation.tailrec
import scala.collection.immutable.Stream.Empty

trait StreamInstances {
implicit val streamInstance: Traverse[Stream] with MonadCombine[Stream] with CoflatMap[Stream] =
Expand Down Expand Up @@ -50,4 +50,24 @@ trait StreamInstances {
G.map(gsb)(_.value)
}
}

// TODO: eventually use algebra's instances (which will deal with
// implicit priority between Eq/PartialOrder/Order).

implicit def eqStream[A](implicit ev: Eq[A]): Eq[Stream[A]] =
new Eq[Stream[A]] {
def eqv(x: Stream[A], y: Stream[A]): Boolean = {
def loop(xs: Stream[A], ys: Stream[A]): Boolean =
xs match {
case Empty => ys.isEmpty
case a #:: xs =>
ys match {
case Empty => false
case b #:: ys => if (ev.neqv(a, b)) false else loop(xs, ys)
}
}
loop(x, y)
}
}

}
20 changes: 19 additions & 1 deletion std/src/main/scala/cats/std/vector.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cats
package std

import scala.annotation.tailrec
import scala.collection.immutable.VectorBuilder

trait VectorInstances {
Expand Down Expand Up @@ -39,4 +38,23 @@ trait VectorInstances {
G.map(gbb)(_.result)
}
}

// TODO: eventually use algebra's instances (which will deal with
// implicit priority between Eq/PartialOrder/Order).

implicit def eqVector[A](implicit ev: Eq[A]): Eq[Vector[A]] =
new Eq[Vector[A]] {
def eqv(x: Vector[A], y: Vector[A]): Boolean = {
def loop(xs: Vector[A], ys: Vector[A]): Boolean =
xs match {
case Seq() => ys.isEmpty
case a +: xs =>
ys match {
case Seq() => false
case b +: ys => if (ev.neqv(a, b)) false else loop(xs, ys)
}
}
loop(x, y)
}
}
}
6 changes: 5 additions & 1 deletion tests/src/test/scala/cats/tests/StdTests.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package cats.tests

import cats.laws.discipline.{ComonadTests, MonadTests, MonadFilterTests}
import cats.laws.discipline.{ComonadTests, MonadTests, MonadFilterTests, MonoidKTests}

class StdTests extends CatsSuite {
checkAll("Function0[Int]", ComonadTests[Function0].comonad[Int, Int, Int])
checkAll("Function0[Int]", MonadTests[Function0].monad[Int, Int, Int])
checkAll("Option[Int]", MonadFilterTests[Option].monadFilter[Int, Int, Int])
checkAll("Option[String]", MonadFilterTests[Option].monadFilter[String, Int, Int])
checkAll("Option[Int]", MonoidKTests[Option].monoidK[Int])
checkAll("List[Int]", MonadFilterTests[List].monadFilter[Int, Int, Int])
checkAll("List[Int]", MonoidKTests[List].monoidK[Int])
checkAll("Stream[Int]", MonoidKTests[Stream].monoidK[Int])
checkAll("Vector[Int]", MonoidKTests[Vector].monoidK[Int])
}