Skip to content

Commit

Permalink
Convert HashLaws to new setup
Browse files Browse the repository at this point in the history
  • Loading branch information
Luka Jacobowitz committed Oct 4, 2017
1 parent 8c60716 commit 8dbbb81
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 119 deletions.
66 changes: 19 additions & 47 deletions kernel-laws/src/main/scala/cats/kernel/laws/HashLaws.scala
Original file line number Diff line number Diff line change
@@ -1,55 +1,27 @@
package cats.kernel
package laws

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

import scala.util.hashing._

object HashLaws {
def apply[A : Eq : Arbitrary]: HashLaws[A] =
new HashLaws[A] {
def Equ = implicitly[Eq[A]]
def Arb = implicitly[Arbitrary[A]]
}
}
trait HashLaws[A] extends EqLaws[A] {
override implicit def E: Hash[A]

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


/**
* @author Tongfei Chen
*/
trait HashLaws[A] extends Laws {

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

def hash(implicit A: Hash[A]): HashProperties = new HashProperties(
name = "hash",
parent = None,
"compatibility-hash" -> forAll { (x: A, y: A) =>
!(A.eqv(x, y)) || (Hash.hash(x) == Hash.hash(y))
}
)

def sameAsUniversalHash(implicit A: Hash[A]): HashProperties = new HashProperties(
name = "sameAsUniversalHash",
parent = None,
"same-as-universal-hash" -> forAll { (x: A, y: A) =>
(A.hash(x) == x.hashCode) && (Hash.fromUniversalHashCode[A].hash(x) == x.hashCode()) &&
(A.eqv(x, y) == Hash.fromUniversalHashCode[A].eqv(x, y))
}
)

def sameAsScalaHashing(implicit catsHash: Hash[A], scalaHashing: Hashing[A]): HashProperties = new HashProperties(
name = "sameAsScalaHashing",
parent = None,
"same-as-scala-hashing" -> forAll { (x: A, y: A) =>
(catsHash.hash(x) == Hash.fromHashing(scalaHashing).hash(x)) &&
(catsHash.eqv(x, y) == Hash.fromHashing(scalaHashing).eqv(x, y))
}
)

class HashProperties(name: String, parent: Option[RuleSet], props: (String, Prop)*)
extends DefaultRuleSet(name, parent, props: _*)
def sameAsUniversalHash (x: A, y: A): IsEq[Boolean] =
((E.hash(x) == x.hashCode) && (Hash.fromUniversalHashCode[A].hash(x) == x.hashCode()) &&
(E.eqv(x, y) == Hash.fromUniversalHashCode[A].eqv(x, y))) <-> true


def sameAsScalaHashing(x: A, y: A, scalaHashing: Hashing[A]): IsEq[Boolean] =
((E.hash(x) == Hash.fromHashing(scalaHashing).hash(x)) &&
(E.eqv(x, y) == Hash.fromHashing(scalaHashing).eqv(x, y))) <-> true

}

object HashLaws {
def apply[A](implicit ev: Hash[A]): HashLaws[A] =
new HashLaws[A] { def E: Hash[A] = ev }
}
2 changes: 1 addition & 1 deletion kernel-laws/src/main/scala/cats/kernel/laws/IsEq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ package cats.kernel
package laws

/** Represents two values of the same type that are expected to be equal. */
final case class IsEq[A](lhs: A, rhs: A)
final case class IsEq[A](lhs: A, rhs: A)
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cats
package kernel
package laws
package discipline

import cats.kernel.instances.boolean._
import org.scalacheck.Arbitrary
import org.scalacheck.Prop.forAll

import scala.util.hashing.Hashing

trait HashTests[A] extends EqLawTests[A] {

def laws: HashLaws[A]

def hash(implicit arbA: Arbitrary[A], arbF: Arbitrary[A => A], eqA: Eq[A], hashA: Hashing[A]): RuleSet =
new DefaultRuleSet(
"hash",
Some(eqv),
"hash compatibility" -> forAll(laws.hashCompatibility _),
"same as universal hash" -> forAll(laws.sameAsUniversalHash _),
"same as universal hash" -> forAll((x: A, y: A) => laws.sameAsScalaHashing(x, y, hashA))
)

}

object HashTests {
def apply[A: Hash]: HashTests[A] =
new HashTests[A] { def laws: HashLaws[A] = HashLaws[A] }
}
95 changes: 26 additions & 69 deletions kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import java.util.concurrent.TimeUnit.{DAYS, HOURS, MINUTES, SECONDS, MILLISECOND

object KernelCheck {

implicit def hashLaws[A: Cogen: Eq: Arbitrary]: HashLaws[A] = HashLaws[A]

implicit val arbitraryBitSet: Arbitrary[BitSet] =
Arbitrary(arbitrary[List[Short]].map(ns => BitSet(ns.map(_ & 0xffff): _*)))
Expand Down Expand Up @@ -174,83 +173,41 @@ class LawTests extends FunSuite with Discipline {
checkAll("CommutativeGroup[Int]", SerializableTests.serializable(CommutativeGroup[Int]))
checkAll("CommutativeGroup[Long]", CommutativeGroupTests[Long].commutativeGroup)
checkAll("CommutativeGroup[Long]", SerializableTests.serializable(CommutativeGroup[Long]))
//checkAll("CommutativeGroup[Float]", CommutativeGroupTests[Float].commutativeGroup) // approximately associative
//checkAll("CommutativeGroup[Double]", CommutativeGroupTests[Double].commutativeGroup) // approximately associative
// checkAll("CommutativeGroup[Float]", CommutativeGroupTests[Float].commutativeGroup) // approximately associative
// checkAll("CommutativeGroup[Double]", CommutativeGroupTests[Double].commutativeGroup) // approximately associative
checkAll("CommutativeGroup[BigInt]", CommutativeGroupTests[BigInt].commutativeGroup)
checkAll("CommutativeGroup[BigInt]", SerializableTests.serializable(CommutativeGroup[BigInt]))
checkAll("CommutativeGroup[Duration]", CommutativeGroupTests[Duration].commutativeGroup)
checkAll("CommutativeGroup[Duration]", SerializableTests.serializable(CommutativeGroup[Duration]))


laws[HashLaws, Unit].check(_.hash)
laws[HashLaws, Boolean].check(_.hash)
laws[HashLaws, String].check(_.hash)
laws[HashLaws, Symbol].check(_.hash)
laws[HashLaws, Byte].check(_.hash)
laws[HashLaws, Short].check(_.hash)
laws[HashLaws, Char].check(_.hash)
laws[HashLaws, Int].check(_.hash)
laws[HashLaws, Float].check(_.hash)
laws[HashLaws, Double].check(_.hash)
laws[HashLaws, Long].check(_.hash)
laws[HashLaws, BitSet].check(_.hash)
laws[HashLaws, BigDecimal].check(_.hash)
laws[HashLaws, BigInt].check(_.hash)
laws[HashLaws, UUID].check(_.hash)
laws[HashLaws, List[Int]].check(_.hash)
laws[HashLaws, Option[String]].check(_.hash)
laws[HashLaws, List[String]].check(_.hash)
laws[HashLaws, Vector[Int]].check(_.hash)
laws[HashLaws, Stream[Int]].check(_.hash)
laws[HashLaws, Set[Int]].check(_.hash)
laws[HashLaws, (Int, String)].check(_.hash)
laws[HashLaws, Either[Int, String]].check(_.hash)
laws[HashLaws, Map[Int, String]].check(_.hash)

laws[HashLaws, Unit].check(_.sameAsUniversalHash)
laws[HashLaws, Boolean].check(_.sameAsUniversalHash)
laws[HashLaws, String].check(_.sameAsUniversalHash)
laws[HashLaws, Symbol].check(_.sameAsUniversalHash)
laws[HashLaws, Byte].check(_.sameAsUniversalHash)
laws[HashLaws, Short].check(_.sameAsUniversalHash)
laws[HashLaws, Char].check(_.sameAsUniversalHash)
laws[HashLaws, Int].check(_.sameAsUniversalHash)
laws[HashLaws, Float].check(_.sameAsUniversalHash)
laws[HashLaws, Double].check(_.sameAsUniversalHash)
laws[HashLaws, Long].check(_.sameAsUniversalHash)
laws[HashLaws, BitSet].check(_.sameAsUniversalHash)
laws[HashLaws, BigDecimal].check(_.sameAsUniversalHash)
laws[HashLaws, BigInt].check(_.sameAsUniversalHash)
laws[HashLaws, UUID].check(_.sameAsUniversalHash)
laws[HashLaws, List[Int]].check(_.sameAsUniversalHash)
laws[HashLaws, Option[String]].check(_.sameAsUniversalHash)
laws[HashLaws, List[String]].check(_.sameAsUniversalHash)
laws[HashLaws, Vector[Int]].check(_.sameAsUniversalHash)
laws[HashLaws, Stream[Int]].check(_.sameAsUniversalHash)
laws[HashLaws, Set[Int]].check(_.sameAsUniversalHash)
laws[HashLaws, (Int, String)].check(_.sameAsUniversalHash)
laws[HashLaws, Either[Int, String]].check(_.sameAsUniversalHash)
laws[HashLaws, Map[Int, String]].check(_.sameAsUniversalHash)
checkAll("Hash[Unit]" , HashTests[Unit].hash)
checkAll("Hash[Boolean]" , HashTests[Boolean].hash)
checkAll("Hash[String]" , HashTests[String].hash)
checkAll("Hash[Symbol]" , HashTests[Symbol].hash)
checkAll("Hash[Byte]" , HashTests[Byte].hash)
checkAll("Hash[Short]" , HashTests[Short].hash)
checkAll("Hash[Char]" , HashTests[Char].hash)
checkAll("Hash[Int]" , HashTests[Int].hash)

// NOTE: Do not test for Float/Double/Long. These types'
// `##` is different from `hashCode`. See [[scala.runtime.Statics.anyHash]].
laws[HashLaws, Unit].check(_.sameAsScalaHashing)
laws[HashLaws, Boolean].check(_.sameAsScalaHashing)
laws[HashLaws, String].check(_.sameAsScalaHashing)
laws[HashLaws, Symbol].check(_.sameAsScalaHashing)
laws[HashLaws, Byte].check(_.sameAsScalaHashing)
laws[HashLaws, Short].check(_.sameAsScalaHashing)
laws[HashLaws, Char].check(_.sameAsScalaHashing)
laws[HashLaws, Int].check(_.sameAsScalaHashing)
laws[HashLaws, BitSet].check(_.sameAsScalaHashing)
laws[HashLaws, BigDecimal].check(_.sameAsScalaHashing)
laws[HashLaws, BigInt].check(_.sameAsScalaHashing)
laws[HashLaws, UUID].check(_.sameAsScalaHashing)

laws[HashLaws, Option[HasHash[Int]]].check(_.hash)
laws[HashLaws, List[HasHash[Int]]].check(_.hash)
laws[HashLaws, Vector[HasHash[Int]]].check(_.hash)
laws[HashLaws, Stream[HasHash[Int]]].check(_.hash)
// checkAll("Hash[Float]" , HashTests[Float].hash)
// checkAll("Hash[Double]" , HashTests[Double].hash)
checkAll("Hash[BitSet]" , HashTests[BitSet].hash)
checkAll("Hash[BigDecimal]" , HashTests[BigDecimal].hash)
checkAll("Hash[BigInt]" , HashTests[BigInt].hash)
checkAll("Hash[UUID]" , HashTests[UUID].hash)
checkAll("Hash[List[Int]]" , HashTests[List[Int]].hash)
checkAll("Hash[Option[String]]" , HashTests[Option[String]].hash)
checkAll("Hash[List[String]]" , HashTests[List[String]].hash)
checkAll("Hash[Vector[Int]]" , HashTests[Vector[Int]].hash)
checkAll("Hash[Stream[Int]]" , HashTests[Stream[Int]].hash)
checkAll("Hash[Set[Int]]" , HashTests[Set[Int]].hash)
checkAll("Hash[(Int, String)]" , HashTests[(Int, String)].hash)
checkAll("Hash[Either[Int, String]]" , HashTests[Either[Int, String]].hash)
checkAll("Hash[Map[Int, String]]" , HashTests[Map[Int, String]].hash)



{
Expand Down
2 changes: 0 additions & 2 deletions tests/src/test/scala/cats/tests/FunctionTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import cats.kernel.laws.discipline.{SerializableTests, _}
import cats.laws.discipline._
import cats.laws.discipline.eq._
import cats.laws.discipline.arbitrary._
import cats.kernel.laws._
import cats.kernel.{CommutativeGroup, CommutativeMonoid, CommutativeSemigroup}
import cats.kernel.{Band, BoundedSemilattice, Semilattice}

Expand Down Expand Up @@ -45,7 +44,6 @@ class FunctionTests extends CatsSuite {
checkAll("Function0[Eqed]", EqLawTests[Function0[Eqed]].eqv)
checkAll("Function0[POrd]", PartialOrderLawTests[Function0[POrd]].partialOrder)
checkAll("Function0[Ord]", OrderLawTests[Function0[Ord]].order)
checkAll("Function0[Hsh]", HashLaws[Function0[Hsh]].hash)
checkAll("Function0[Semi]", SemigroupLawTests[Function0[Semi]].semigroup)
checkAll("Function0[CSemi]", CommutativeSemigroupTests[Function0[CSemi]].commutativeSemigroup)
checkAll("Function0[Bnd]", BandTests[Function0[Bnd]].band)
Expand Down

0 comments on commit 8dbbb81

Please sign in to comment.