diff --git a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala index a55b709d68..bc5f011e09 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -189,6 +189,7 @@ class LawTests extends FunSuite with Discipline { checkAll("Hash[Short]" , HashTests[Short].hash) checkAll("Hash[Char]" , HashTests[Char].hash) checkAll("Hash[Int]" , HashTests[Int].hash) + checkAll("Hash[Duration]", HashTests[Duration].hash) // NOTE: Do not test for Float/Double/Long. These types' // `##` is different from `hashCode`. See [[scala.runtime.Statics.anyHash]]. @@ -207,6 +208,7 @@ class LawTests extends FunSuite with Discipline { 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) + checkAll("Hash[Queue[Int]", HashTests[Queue[Int]].hash) diff --git a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala index 85944bc1ea..4b47e9da24 100644 --- a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala +++ b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala @@ -96,4 +96,34 @@ object StaticMethods { h = mix(h, _2Hash) finalizeHash(h, 2) } + + // adapted from [[scala.util.hashing.MurmurHash3]], + // but modified standard `Any#hashCode` to `ev.hash`. + def listHash[A](x: List[A])(implicit A: Hash[A]): Int = { + import scala.util.hashing.MurmurHash3._ + var n = 0 + var h = seqSeed + var elems = x + while (!elems.isEmpty) { + val head = elems.head + val tail = elems.tail + h = mix(h, A.hash(head)) + n += 1 + elems = tail + } + finalizeHash(h, n) + } + + // adapted from scala.util.hashing.MurmurHash3 + def orderedHash[A](xs: TraversableOnce[A])(implicit A: Hash[A]): Int = { + import scala.util.hashing.MurmurHash3._ + var n = 0 + var h = seqSeed + xs foreach { x => + h = mix(h, A.hash(x)) + n += 1 + } + finalizeHash(h, n) + } + } diff --git a/kernel/src/main/scala/cats/kernel/instances/duration.scala b/kernel/src/main/scala/cats/kernel/instances/duration.scala index fbbfca600e..406ed025e7 100644 --- a/kernel/src/main/scala/cats/kernel/instances/duration.scala +++ b/kernel/src/main/scala/cats/kernel/instances/duration.scala @@ -6,7 +6,7 @@ import scala.concurrent.duration.Duration package object duration extends DurationInstances trait DurationInstances { - implicit val catsKernelStdOrderForDuration: Order[Duration] = new DurationOrder + implicit val catsKernelStdOrderForDuration: Order[Duration] with Hash[Duration] = new DurationOrder implicit val catsKernelStdGroupForDuration: CommutativeGroup[Duration] = new DurationGroup } @@ -18,7 +18,9 @@ trait DurationInstances { * The value Duration.Undefined breaks our laws, because undefined * values are not equal to themselves. */ -class DurationOrder extends Order[Duration] { +class DurationOrder extends Order[Duration] with Hash[Duration] { + def hash(x: Duration): Int = x.hashCode() + def compare(x: Duration, y: Duration): Int = x compare y override def eqv(x: Duration, y: Duration): Boolean = x == y diff --git a/kernel/src/main/scala/cats/kernel/instances/list.scala b/kernel/src/main/scala/cats/kernel/instances/list.scala index afdf3c97d4..7e15e0bf05 100644 --- a/kernel/src/main/scala/cats/kernel/instances/list.scala +++ b/kernel/src/main/scala/cats/kernel/instances/list.scala @@ -62,22 +62,7 @@ class ListPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Lis } class ListHash[A](implicit ev: Hash[A]) extends ListEq[A]()(ev) with Hash[List[A]] { - // adapted from [[scala.util.hashing.MurmurHash3]], - // but modified standard `Any#hashCode` to `ev.hash`. - import scala.util.hashing.MurmurHash3._ - def hash(x: List[A]): Int = { - var n = 0 - var h = seqSeed - var elems = x - while (!elems.isEmpty) { - val head = elems.head - val tail = elems.tail - h = mix(h, ev.hash(head)) - n += 1 - elems = tail - } - finalizeHash(h, n) - } + def hash(x: List[A]): Int = StaticMethods.listHash(x)(ev) } class ListEq[A](implicit ev: Eq[A]) extends Eq[List[A]] { diff --git a/kernel/src/main/scala/cats/kernel/instances/queue.scala b/kernel/src/main/scala/cats/kernel/instances/queue.scala index 2018e9f300..af93560b22 100644 --- a/kernel/src/main/scala/cats/kernel/instances/queue.scala +++ b/kernel/src/main/scala/cats/kernel/instances/queue.scala @@ -15,6 +15,9 @@ trait QueueInstances extends QueueInstances1 { trait QueueInstances1 extends QueueInstances2 { implicit def catsKernelStdPartialOrderForQueue[A: PartialOrder]: PartialOrder[Queue[A]] = new QueuePartialOrder[A] + + implicit def catsKernelStdHashForQueue[A: Hash]: Hash[Queue[A]] = + new QueueHash[A] } trait QueueInstances2 { @@ -28,6 +31,10 @@ class QueueOrder[A](implicit ev: Order[A]) extends Order[Queue[A]] { else StaticMethods.iteratorCompare(xs.iterator, ys.iterator) } +class QueueHash[A](implicit ev: Hash[A]) extends QueueEq[A] with Hash[Queue[A]] { + def hash(x: Queue[A]): Int = StaticMethods.orderedHash(x) +} + class QueuePartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[Queue[A]] { def partialCompare(xs: Queue[A], ys: Queue[A]): Double = if (xs eq ys) 0.0 diff --git a/kernel/src/main/scala/cats/kernel/instances/stream.scala b/kernel/src/main/scala/cats/kernel/instances/stream.scala index 3d001b43ba..da7201c267 100644 --- a/kernel/src/main/scala/cats/kernel/instances/stream.scala +++ b/kernel/src/main/scala/cats/kernel/instances/stream.scala @@ -36,17 +36,7 @@ class StreamPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[S } class StreamHash[A](implicit ev: Hash[A]) extends StreamEq[A]()(ev) with Hash[Stream[A]] { - import scala.util.hashing.MurmurHash3._ - // adapted from scala.util.hashing.MurmurHash3 - def hash(xs: Stream[A]): Int = { - var n = 0 - var h = seqSeed - xs foreach { x => - h = mix(h, ev.hash(x)) - n += 1 - } - finalizeHash(h, n) - } + def hash(xs: Stream[A]): Int = StaticMethods.orderedHash(xs) } class StreamEq[A](implicit ev: Eq[A]) extends Eq[Stream[A]] { diff --git a/kernel/src/main/scala/cats/kernel/instances/vector.scala b/kernel/src/main/scala/cats/kernel/instances/vector.scala index cc08e0fe6c..c7b15101e8 100644 --- a/kernel/src/main/scala/cats/kernel/instances/vector.scala +++ b/kernel/src/main/scala/cats/kernel/instances/vector.scala @@ -35,18 +35,8 @@ class VectorPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[V else StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) } -class VectorHash[A](implicit ev: Hash[A]) extends VectorEq[A]()(ev) with Hash[Vector[A]] { - // adapted from scala.util.hashing - import scala.util.hashing.MurmurHash3._ - def hash(xs: Vector[A]): Int = { - var n = 0 - var h = seqSeed - xs foreach { x => - h = mix(h, ev.hash(x)) - n += 1 - } - finalizeHash(h, n) - } +class VectorHash[A](implicit ev: Hash[A]) extends VectorEq[A] with Hash[Vector[A]] { + def hash(xs: Vector[A]): Int = StaticMethods.orderedHash(xs) } class VectorEq[A](implicit ev: Eq[A]) extends Eq[Vector[A]] {