Skip to content

Commit

Permalink
Make kernel laws consistent with core laws (#1922)
Browse files Browse the repository at this point in the history
* Make GroupLaws consistent with cats-core

* Make OrderLaws consistent with core

* Port tests

* Update lawtesting docs

* Remove unused machinery

* Remove duplication of IsEq

* Cleanup

* Add Serializable to LawTests

* Rename to -LawTests

* Fix BoundedSemilattice parents

* Add id

* Fix lawtesting docs

* Restructure order law tests

* Move IsEq

* Convert HashLaws to new setup

* Readd function0 hash test

* Add mima exception

* Rename to ascending order
  • Loading branch information
LukaJCB authored and kailuowang committed Oct 6, 2017
1 parent 4ffd3a1 commit 9ae4dc4
Show file tree
Hide file tree
Showing 65 changed files with 1,011 additions and 763 deletions.
6 changes: 5 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ val binaryCompatibleExceptions = {
exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.MapInstances.catsKernelStdEqForMap"),
exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.MapInstances.catsKernelStdMonoidForMap"),
exclude[ReversedMissingMethodProblem]("cats.kernel.instances.MapInstances.catsKernelStdHashForMap"),
exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.OptionInstances0.catsKernelStdEqForOption"),
exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple12"),
exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple7"),
exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple4"),
Expand Down Expand Up @@ -349,10 +350,13 @@ val binaryCompatibleExceptions = {
exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.EitherInstances0.catsStdPartialOrderForEither"),
exclude[ReversedMissingMethodProblem]("cats.kernel.instances.OptionInstances1.catsKernelStdHashForOption"),
exclude[ReversedMissingMethodProblem]("cats.kernel.instances.FunctionInstances0.catsKernelHashForFunction0"),
exclude[ReversedMissingMethodProblem]("cats.kernel.instances.OptionInstances0.catsKernelStdPartialOrderForOption"),
exclude[ReversedMissingMethodProblem]("cats.kernel.instances.EitherInstances0.catsStdHashForEither"),
exclude[DirectMissingMethodProblem]("cats.kernel.instances.OptionInstances1.catsKernelStdPartialOrderForOption"),
exclude[DirectMissingMethodProblem]("cats.kernel.instances.all.package.catsKernelStdPartialOrderForBitSet"),
exclude[DirectMissingMethodProblem]("cats.kernel.instances.bitSet.package.catsKernelStdPartialOrderForBitSet"),
//todo: remove these once we release 1.0.0-RC1
exclude[MissingTypesProblem]("cats.kernel.instances.OptionInstances1"),
exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.OptionInstances1.catsKernelStdHashForOption"),
exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances.*"),
exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances1.*"),
exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances2.*"),
Expand Down
10 changes: 4 additions & 6 deletions docs/src/main/tut/typeclasses/lawtesting.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,8 @@ And voila, you've successfully proven that your data type upholds the Functor la
### Testing cats.kernel instances

For most of the type classes included in cats, the above will work great.
However, the law tests for the type classes inside `cats.kernel` are structured a bit differently.
These include `Semigroup`, `Monoid`, `Group` and `Semilattice`.
Instead of importing the laws from `cats.laws.discipline.*`, we have to import `cats.kernel.laws.GroupLaws`
and then call the corresponding method, e.g. `GroupLaws[Foo].monoid`, or `GroupLaws[Foo].semigroup`.
However, the law tests for the type classes inside `cats.kernel` are located in `cats.kernel.laws.discipline.*` instead.
So we have to import from there to test type classes like `Semigroup`, `Monoid`, `Group` or `Semilattice`.

Let's test it out, by defining a `Semigroup` instance for our `Tree` type.

Expand All @@ -135,10 +133,10 @@ Then we can again test the instance inside our class extending `CatsSuite`:

```tut:book
import cats.laws.discipline.FunctorTests
import cats.kernel.laws.GroupLaws
import cats.kernel.laws.discipline.SemigroupLawTests
class TreeLawTests extends CatsSuite {
checkAll("Tree[Int].MonoidLaws", GroupLaws[Tree[Int]].semigroup)
checkAll("Tree[Int].SemigroupLaws", SemigroupLawTests[Tree[Int]].semigroup)
checkAll("Tree.FunctorLaws", FunctorTests[Tree].functor[Int, Int, String])
}
```
6 changes: 3 additions & 3 deletions js/src/test/scala/cats/tests/FutureTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package js
package tests

import cats.kernel.laws.GroupLaws
import cats.kernel.laws.discipline.{MonoidLawTests, SemigroupLawTests}
import cats.laws.discipline._
import cats.js.instances.Await
import cats.js.instances.future.futureComonad
Expand Down Expand Up @@ -58,8 +58,8 @@ class FutureTests extends CatsSuite {

{
implicit val F = ListWrapper.semigroup[Int]
checkAll("Future[ListWrapper[Int]]", GroupLaws[Future[ListWrapper[Int]]].semigroup)
checkAll("Future[ListWrapper[Int]]", SemigroupLawTests[Future[ListWrapper[Int]]].semigroup)
}

checkAll("Future[Int]", GroupLaws[Future[Int]].monoid)
checkAll("Future[Int]", MonoidLawTests[Future[Int]].monoid)
}
6 changes: 3 additions & 3 deletions jvm/src/test/scala/cats/tests/FutureTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package jvm
package tests

import cats.kernel.laws.GroupLaws
import cats.kernel.laws.discipline.{MonoidLawTests, SemigroupLawTests}
import cats.laws.discipline._
import cats.laws.discipline.arbitrary._
import cats.tests.{CatsSuite, ListWrapper}
Expand Down Expand Up @@ -44,8 +44,8 @@ class FutureTests extends CatsSuite {

{
implicit val F = ListWrapper.semigroup[Int]
checkAll("Future[ListWrapper[Int]]", GroupLaws[Future[ListWrapper[Int]]].semigroup)
checkAll("Future[ListWrapper[Int]]", SemigroupLawTests[Future[ListWrapper[Int]]].semigroup)
}

checkAll("Future[Int]", GroupLaws[Future[Int]].monoid)
checkAll("Future[Int]", MonoidLawTests[Future[Int]].monoid)
}
16 changes: 16 additions & 0 deletions kernel-laws/src/main/scala/cats/kernel/laws/BandLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cats.kernel.laws

import cats.kernel.Band

trait BandLaws[A] extends SemigroupLaws[A] {
override implicit def S: Band[A]

def idempotence(x: A): IsEq[A] =
S.combine(x, x) <-> x

}

object BandLaws {
def apply[A](implicit ev: Band[A]): BandLaws[A] =
new BandLaws[A] { def S: Band[A] = ev }
}
27 changes: 0 additions & 27 deletions kernel-laws/src/main/scala/cats/kernel/laws/BaseLaws.scala

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cats.kernel.laws

import cats.kernel.BoundedSemilattice

trait BoundedSemilatticeLaws[A] extends CommutativeMonoidLaws[A] with SemilatticeLaws[A] {
override implicit def S: BoundedSemilattice[A]

}

object BoundedSemilatticeLaws {
def apply[A](implicit ev: BoundedSemilattice[A]): BoundedSemilatticeLaws[A] =
new BoundedSemilatticeLaws[A] { def S: BoundedSemilattice[A] = ev }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cats
package kernel
package laws


trait CommutativeGroupLaws[A] extends GroupLaws[A] with CommutativeMonoidLaws[A] {
override implicit def S: CommutativeGroup[A]
}

object CommutativeGroupLaws {
def apply[A](implicit ev: CommutativeGroup[A]): CommutativeGroupLaws[A] =
new CommutativeGroupLaws[A] { def S: CommutativeGroup[A] = ev }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cats.kernel.laws

import cats.kernel.CommutativeMonoid

trait CommutativeMonoidLaws[A] extends MonoidLaws[A] with CommutativeSemigroupLaws[A] {
override implicit def S: CommutativeMonoid[A]

}

object CommutativeMonoidLaws {
def apply[A](implicit ev: CommutativeMonoid[A]): CommutativeMonoidLaws[A] =
new CommutativeMonoidLaws[A] { def S: CommutativeMonoid[A] = ev }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package cats.kernel.laws

import cats.kernel.CommutativeSemigroup

trait CommutativeSemigroupLaws[A] extends SemigroupLaws[A] {
override implicit def S: CommutativeSemigroup[A]

def commutative(x: A, y: A): IsEq[A] =
S.combine(x, y) <-> S.combine(y, x)

}

object CommutativeSemigroupLaws {
def apply[A](implicit ev: CommutativeSemigroup[A]): CommutativeSemigroupLaws[A] =
new CommutativeSemigroupLaws[A] { def S: CommutativeSemigroup[A] = ev }
}
26 changes: 26 additions & 0 deletions kernel-laws/src/main/scala/cats/kernel/laws/EqLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cats.kernel.laws

import cats.kernel.Eq

trait EqLaws[A] {

implicit def E: Eq[A]

def reflexitivityEq(x: A): IsEq[A] =
x <-> x

def symmetryEq(x: A, y: A): IsEq[Boolean] =
E.eqv(x, y) <-> E.eqv(y, x)

def antiSymmetryEq(x: A, y: A, f: A => A): IsEq[Boolean] =
(!E.eqv(x, y) || E.eqv(f(x), f(y))) <-> true

def transitivityEq(x: A, y: A, z: A): IsEq[Boolean] =
(!(E.eqv(x, y) && E.eqv(y, z)) || E.eqv(x, z)) <-> true

}

object EqLaws {
def apply[A](implicit ev: Eq[A]): EqLaws[A] =
new EqLaws[A] { def E: Eq[A] = ev }
}
104 changes: 13 additions & 91 deletions kernel-laws/src/main/scala/cats/kernel/laws/GroupLaws.scala
Original file line number Diff line number Diff line change
@@ -1,101 +1,23 @@
package cats.kernel
package laws

import cats.kernel._
import cats.kernel.instances.option._

import org.typelevel.discipline.Laws
trait GroupLaws[A] extends MonoidLaws[A] {
override implicit def S: Group[A]

import org.scalacheck.{Arbitrary, Prop}
import org.scalacheck.Prop._
def leftInverse(x: A): IsEq[A] =
S.empty <-> S.combine(S.inverse(x), x)

object GroupLaws {
def apply[A : Eq : Arbitrary]: GroupLaws[A] = new GroupLaws[A] {
def Equ = Eq[A]
def Arb = implicitly[Arbitrary[A]]
}
}

trait GroupLaws[A] extends Laws {

implicit def Equ: Eq[A]
implicit def Arb: Arbitrary[A]

// groups

def semigroup(implicit A: Semigroup[A]): GroupProperties = new GroupProperties(
name = "semigroup",
parents = Nil,
Rules.serializable(A),
Rules.associativity(A.combine),
Rules.repeat1("combineN")(A.combineN),
Rules.repeat2("combineN", "|+|")(A.combineN)(A.combine),
"combineAllOption" -> forAll { (xs: Vector[A]) =>
A.combineAllOption(xs) ?== xs.reduceOption(A.combine)
}
)

def band(implicit A: Band[A]): GroupProperties = new GroupProperties(
name = "band",
parents = List(semigroup),
Rules.idempotence(A.combine),
"isIdempotent" -> Semigroup.isIdempotent[A]
)

def commutativeSemigroup(implicit A: CommutativeSemigroup[A]): GroupProperties = new GroupProperties(
name = "commutative semigroup",
parents = List(semigroup),
Rules.commutative(A.combine)
)
def rightInverse(x: A): IsEq[A] =
S.empty <-> S.combine(x, S.inverse(x))

def semilattice(implicit A: Semilattice[A]): GroupProperties = new GroupProperties(
name = "semilattice",
parents = List(band, commutativeSemigroup)
)

def monoid(implicit A: Monoid[A]): GroupProperties = new GroupProperties(
name = "monoid",
parents = List(semigroup),
Rules.leftIdentity(A.empty)(A.combine),
Rules.rightIdentity(A.empty)(A.combine),
Rules.repeat0("combineN", "id", A.empty)(A.combineN),
Rules.collect0("combineAll", "id", A.empty)(A.combineAll),
Rules.isId("isEmpty", A.empty)(A.isEmpty),
"combineAll" -> forAll { (xs: Vector[A]) =>
A.combineAll(xs) ?== (A.empty +: xs).reduce(A.combine)
}
)

def commutativeMonoid(implicit A: CommutativeMonoid[A]): GroupProperties = new GroupProperties(
name = "commutative monoid",
parents = List(monoid, commutativeSemigroup)
)

def boundedSemilattice(implicit A: BoundedSemilattice[A]): GroupProperties = new GroupProperties(
name = "boundedSemilattice",
parents = List(commutativeMonoid, semilattice)
)

def group(implicit A: Group[A]): GroupProperties = new GroupProperties(
name = "group",
parents = List(monoid),
Rules.leftInverse(A.empty)(A.combine)(A.inverse),
Rules.rightInverse(A.empty)(A.combine)(A.inverse),
Rules.consistentInverse("remove")(A.remove)(A.combine)(A.inverse)
)
def consistentInverse(x: A, y: A): IsEq[A] =
S.remove(x, y) <-> S.combine(x, S.inverse(y))
}

def commutativeGroup(implicit A: CommutativeGroup[A]): GroupProperties = new GroupProperties(
name = "commutative group",
parents = List(group, commutativeMonoid)
)
object GroupLaws {
def apply[A](implicit ev: Group[A]): GroupLaws[A] =
new GroupLaws[A] { def S: Group[A] = ev }
}

// property classes

class GroupProperties(
val name: String,
val parents: Seq[GroupProperties],
val props: (String, Prop)*
) extends RuleSet {
val bases = Nil
}
}
Loading

0 comments on commit 9ae4dc4

Please sign in to comment.