Skip to content

Commit 16dba03

Browse files
committed
Merge pull request #198 from lvicentesanchez/issues/196
Add SemigroupK and MonoidK laws (address #196).
2 parents 1f156bd + 17eee17 commit 16dba03

File tree

8 files changed

+152
-3
lines changed

8 files changed

+152
-3
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package cats
2+
package laws
3+
4+
/**
5+
* Laws that must be obeyed by any [[cats.MonoidK]].
6+
*/
7+
trait MonoidKLaws[F[_]] extends SemigroupKLaws[F] {
8+
override implicit def F: MonoidK[F]
9+
10+
def leftIdentity[A](a: F[A]): IsEq[F[A]] =
11+
F.combine(F.empty, a) <-> a
12+
13+
def rightIdentity[A](a: F[A]): IsEq[F[A]] =
14+
F.combine(a, F.empty) <-> a
15+
}
16+
17+
object MonoidKLaws {
18+
def apply[F[_]](implicit ev: MonoidK[F]): MonoidKLaws[F] =
19+
new MonoidKLaws[F] { def F = ev }
20+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package cats
2+
package laws
3+
4+
/**
5+
* Laws that must be obeyed by any [[cats.SemigroupK]].
6+
*/
7+
trait SemigroupKLaws[F[_]] {
8+
implicit def F: SemigroupK[F]
9+
10+
def associative[A](a: F[A], b: F[A], c: F[A]): IsEq[F[A]] =
11+
F.combine(F.combine(a, b), c) <-> F.combine(a, F.combine(b, c))
12+
}
13+
14+
object SemigroupKLaws {
15+
def apply[F[_]](implicit ev: SemigroupK[F]): SemigroupKLaws[F] =
16+
new SemigroupKLaws[F] { def F = ev }
17+
}

laws/src/main/scala/cats/laws/discipline/ArbitraryK.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,10 @@ object ArbitraryK {
5555
def synthesize[A](implicit A: Arbitrary[A]): Arbitrary[Future[A]] =
5656
Arbitrary(A.arbitrary.map(Future.successful))
5757
}
58+
59+
implicit val stream: ArbitraryK[Stream] =
60+
new ArbitraryK[Stream] { def synthesize[A: Arbitrary]: Arbitrary[Stream[A]] = implicitly }
61+
62+
implicit val vector: ArbitraryK[Vector] =
63+
new ArbitraryK[Vector] { def synthesize[A: Arbitrary]: Arbitrary[Vector[A]] = implicitly }
5864
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cats
2+
package laws
3+
package discipline
4+
5+
import org.scalacheck.Prop._
6+
import org.scalacheck.Arbitrary
7+
8+
trait MonoidKTests[F[_]] extends SemigroupKTests[F] {
9+
def laws: MonoidKLaws[F]
10+
11+
def monoidK[A: Arbitrary](implicit
12+
ArbF: ArbitraryK[F],
13+
EqFA: Eq[F[A]]
14+
): RuleSet = {
15+
implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A]
16+
17+
new RuleSet {
18+
val name = "monoidK"
19+
val bases = Nil
20+
val parents = Seq(semigroupK[A])
21+
val props = Seq(
22+
"left identity" -> forAll(laws.leftIdentity[A] _),
23+
"right identity" -> forAll(laws.rightIdentity[A] _)
24+
)
25+
}
26+
}
27+
}
28+
29+
object MonoidKTests {
30+
def apply[F[_] : MonoidK]: MonoidKTests[F] =
31+
new MonoidKTests[F] { def laws = MonoidKLaws[F] }
32+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package cats
2+
package laws
3+
package discipline
4+
5+
import org.scalacheck.Prop._
6+
import org.scalacheck.Arbitrary
7+
import org.typelevel.discipline.Laws
8+
9+
trait SemigroupKTests[F[_]] extends Laws {
10+
def laws: SemigroupKLaws[F]
11+
12+
def semigroupK[A: Arbitrary](implicit
13+
ArbF: ArbitraryK[F],
14+
EqFA: Eq[F[A]]
15+
): RuleSet = {
16+
implicit def ArbFA: Arbitrary[F[A]] = ArbF.synthesize[A]
17+
18+
new RuleSet {
19+
val name = "semigroupK"
20+
val bases = Nil
21+
val parents = Nil
22+
val props = Seq(
23+
"associative" -> forAll(laws.associative[A] _)
24+
)
25+
}
26+
}
27+
}
28+
29+
object SemigroupKTests {
30+
def apply[F[_]: SemigroupK]: SemigroupKTests[F] =
31+
new SemigroupKTests[F] { def laws = SemigroupKLaws[F] }
32+
}

std/src/main/scala/cats/std/stream.scala

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package cats
22
package std
33

4-
import scala.annotation.tailrec
4+
import scala.collection.immutable.Stream.Empty
55

66
trait StreamInstances {
77
implicit val streamInstance: Traverse[Stream] with MonadCombine[Stream] with CoflatMap[Stream] =
@@ -50,4 +50,24 @@ trait StreamInstances {
5050
G.map(gsb)(_.value)
5151
}
5252
}
53+
54+
// TODO: eventually use algebra's instances (which will deal with
55+
// implicit priority between Eq/PartialOrder/Order).
56+
57+
implicit def eqStream[A](implicit ev: Eq[A]): Eq[Stream[A]] =
58+
new Eq[Stream[A]] {
59+
def eqv(x: Stream[A], y: Stream[A]): Boolean = {
60+
def loop(xs: Stream[A], ys: Stream[A]): Boolean =
61+
xs match {
62+
case Empty => ys.isEmpty
63+
case a #:: xs =>
64+
ys match {
65+
case Empty => false
66+
case b #:: ys => if (ev.neqv(a, b)) false else loop(xs, ys)
67+
}
68+
}
69+
loop(x, y)
70+
}
71+
}
72+
5373
}

std/src/main/scala/cats/std/vector.scala

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cats
22
package std
33

4-
import scala.annotation.tailrec
54
import scala.collection.immutable.VectorBuilder
65

76
trait VectorInstances {
@@ -39,4 +38,23 @@ trait VectorInstances {
3938
G.map(gbb)(_.result)
4039
}
4140
}
41+
42+
// TODO: eventually use algebra's instances (which will deal with
43+
// implicit priority between Eq/PartialOrder/Order).
44+
45+
implicit def eqVector[A](implicit ev: Eq[A]): Eq[Vector[A]] =
46+
new Eq[Vector[A]] {
47+
def eqv(x: Vector[A], y: Vector[A]): Boolean = {
48+
def loop(xs: Vector[A], ys: Vector[A]): Boolean =
49+
xs match {
50+
case Seq() => ys.isEmpty
51+
case a +: xs =>
52+
ys match {
53+
case Seq() => false
54+
case b +: ys => if (ev.neqv(a, b)) false else loop(xs, ys)
55+
}
56+
}
57+
loop(x, y)
58+
}
59+
}
4260
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
package cats.tests
22

3-
import cats.laws.discipline.{ComonadTests, MonadTests, MonadFilterTests}
3+
import cats.laws.discipline.{ComonadTests, MonadTests, MonadFilterTests, MonoidKTests}
44

55
class StdTests extends CatsSuite {
66
checkAll("Function0[Int]", ComonadTests[Function0].comonad[Int, Int, Int])
77
checkAll("Function0[Int]", MonadTests[Function0].monad[Int, Int, Int])
88
checkAll("Option[Int]", MonadFilterTests[Option].monadFilter[Int, Int, Int])
99
checkAll("Option[String]", MonadFilterTests[Option].monadFilter[String, Int, Int])
10+
checkAll("Option[Int]", MonoidKTests[Option].monoidK[Int])
1011
checkAll("List[Int]", MonadFilterTests[List].monadFilter[Int, Int, Int])
12+
checkAll("List[Int]", MonoidKTests[List].monoidK[Int])
13+
checkAll("Stream[Int]", MonoidKTests[Stream].monoidK[Int])
14+
checkAll("Vector[Int]", MonoidKTests[Vector].monoidK[Int])
1115
}

0 commit comments

Comments
 (0)