From 16c62ddebbb7f1ae45e6a8f2c27d263e36216cee Mon Sep 17 00:00:00 2001 From: LukaJCB Date: Tue, 3 Oct 2017 12:48:47 +0200 Subject: [PATCH 1/4] Give higher priority to partial-unification fix --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 86fb1db5b3..2b7f3cc7e5 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,11 @@ For more detail about Cats' motivations, go [here](http://typelevel.org/cats/mot Cats is currently available for Scala 2.10, 2.11 and 2.12, and [Scala.js](http://www.scala-js.org/). -To get started with SBT, simply add the following to your `build.sbt` +In order for Cats to work correctly with regards to type inference you'll sometimes need support for improved type inference. +We strongly recommend you to enable improved type inference using `partial-unification`. +To do so for any supported Scalac version, use this [sbt plugin](https://github.com/fiadliel/sbt-partial-unification#sbt-partial-unification). + +Next, to create the cats dependency, simply add the following to your `build.sbt` file: ```scala @@ -49,9 +53,6 @@ functionality, you can pick-and-choose from amongst these modules * [`alleycats`](https://github.com/non/alleycats): cats instances and classes which are not lawful. * [`mouse`](https://github.com/benhutchison/mouse): a small companion to cats that provides convenient syntax (aka extension methods) -#### Enhancing type inference - - To use cats you'll need sometimes support for improved type inference. To enable it for any supported Scalac version, use this [sbt plugin](https://github.com/fiadliel/sbt-partial-unification#sbt-partial-unification). Release notes for Cats are available in [CHANGES.md](https://github.com/typelevel/cats/blob/master/CHANGES.md). From 49dd777ef905d4821b7964727c762ca1fa0c43b8 Mon Sep 17 00:00:00 2001 From: LukaJCB Date: Tue, 3 Oct 2017 17:25:34 +0200 Subject: [PATCH 2/4] Update to include code snippets. --- README.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2b7f3cc7e5..cc6c1a99e9 100644 --- a/README.md +++ b/README.md @@ -23,12 +23,22 @@ For more detail about Cats' motivations, go [here](http://typelevel.org/cats/mot Cats is currently available for Scala 2.10, 2.11 and 2.12, and [Scala.js](http://www.scala-js.org/). -In order for Cats to work correctly with regards to type inference you'll sometimes need support for improved type inference. -We strongly recommend you to enable improved type inference using `partial-unification`. -To do so for any supported Scalac version, use this [sbt plugin](https://github.com/fiadliel/sbt-partial-unification#sbt-partial-unification). -Next, to create the cats dependency, simply add the following to your `build.sbt` -file: +Cats relies on improved type inference via the fix for [SI-2112](https://issues.scala-lang.org/browse/SI-2712), which is not enabled by default. For **Scala 2.11.9 or later** you should add the following to your `build.sbt`: + +```scala +scalacOptions += "-Ypartial-unification" + +libraryDependencies += "org.typelevel" %% "cats-core" % "1.0.0-MF" +``` + +**Or**, if you need to support older versions of Scala you can use the [sbt-partial-unification](https://github.com/fiadliel/sbt-partial-unification#sbt-partial-unification) plugin which extends support back through **Scala 2.10.6 or later**, to add it, simply add this line to your `plugins.sbt`: + +```scala +addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.0") +``` + +And then create the cats dependency, by adding the following to your `build.sbt`: ```scala libraryDependencies += "org.typelevel" %% "cats-core" % "1.0.0-MF" From a915afbb7fe64237f3dc8510ea5f815cc49b0604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Madsen?= Date: Tue, 3 Oct 2017 22:59:51 +0200 Subject: [PATCH 3/4] Update scalafix project dependencies (#1948) --- scalafix/project/build.properties | 2 +- scalafix/project/plugins.sbt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scalafix/project/build.properties b/scalafix/project/build.properties index 27e88aa115..c091b86ca4 100644 --- a/scalafix/project/build.properties +++ b/scalafix/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.13 +sbt.version=0.13.16 diff --git a/scalafix/project/plugins.sbt b/scalafix/project/plugins.sbt index 623b16f031..20784f6ce3 100644 --- a/scalafix/project/plugins.sbt +++ b/scalafix/project/plugins.sbt @@ -1,4 +1,4 @@ -addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.0-RC2") -addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC3") -addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.6.1") -addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.0.0") +addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.5.2") +addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0-RC12") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.7.0") +addSbtPlugin("org.lyranthe.sbt" % "partial-unification" % "1.1.0") From c131fe408e7a6b016e87eaaee6f66a7cec463c0f Mon Sep 17 00:00:00 2001 From: Tongfei Chen Date: Wed, 4 Oct 2017 04:03:16 -0400 Subject: [PATCH 4/4] Hash typeclass (#1712) * cats.kernel.Hash and related instances (#1690) * Hash laws * all test passed * Hash instances for tuples * introduce implicit precedence in KernelBoiler: Order > PartialOrder > Eq; Hash > Eq. * Add type alias in cats._; Add contravariant instance for `Hash` * HashFunctions extends EqFunctions * Move contravariant instances to separate trait, in accordance with (#1659) * revert name change * move EitherInstances1#EitherEq out * Optimize hash computation on case classes without allocating a new object * fixing the problem in CI build: method catsKernelStdOrderForChar()cats.kernel.instances.CharOrder in trait cats.kernel.instances.CharInstances has a different result type in current version, where it is cats.kernel.Order rather than cats.kernel.instances.CharOrder * Full compliance with how Scala generates hash codes on case classes; +SetHash * Cartesian[Hash] * ContravariantCartesian[Hash] * ContravariantCartesian[Hash]; Hash.fromHashing * style issues * remove unused import * some test cases * some test cases * +test for Contravariant[Hash] * +test for Contravariant[Hash] * Add NEL/NEV one (#1707) `NonEmptyList.one` is analogous to `_.pure[NonEmptyList]`. Alternative for `NonEmptyList.of(x)` where you don't pay the price of the varargs, which isn't used. * move instances into separate trait (#1659) * move instances into separate trait * adding separate objects for piecemeal imports * Adding implicit resolution tests for piecemeal imports for hierarchy * Removing explicit implicitly and using apply method * cats.kernel.Hash and related instances (#1690) * Hash laws * all test passed * Hash instances for tuples * introduce implicit precedence in KernelBoiler: Order > PartialOrder > Eq; Hash > Eq. * Add type alias in cats._; Add contravariant instance for `Hash` * HashFunctions extends EqFunctions * Move contravariant instances to separate trait, in accordance with (#1659) * revert name change * move EitherInstances1#EitherEq out * Optimize hash computation on case classes without allocating a new object * fixing the problem in CI build: method catsKernelStdOrderForChar()cats.kernel.instances.CharOrder in trait cats.kernel.instances.CharInstances has a different result type in current version, where it is cats.kernel.Order rather than cats.kernel.instances.CharOrder * Full compliance with how Scala generates hash codes on case classes; +SetHash * Cartesian[Hash] * ContravariantCartesian[Hash] * ContravariantCartesian[Hash]; Hash.fromHashing * style issues * remove unused import * some test cases * some test cases * +test for Contravariant[Hash] * +test for Contravariant[Hash] * cats.kernel.Hash and related instances (#1690) * Hash laws * all test passed * Hash instances for tuples * introduce implicit precedence in KernelBoiler: Order > PartialOrder > Eq; Hash > Eq. * Add type alias in cats._; Add contravariant instance for `Hash` * HashFunctions extends EqFunctions * Move contravariant instances to separate trait, in accordance with (#1659) * revert name change * move EitherInstances1#EitherEq out * Optimize hash computation on case classes without allocating a new object * fixing the problem in CI build: method catsKernelStdOrderForChar()cats.kernel.instances.CharOrder in trait cats.kernel.instances.CharInstances has a different result type in current version, where it is cats.kernel.Order rather than cats.kernel.instances.CharOrder * Full compliance with how Scala generates hash codes on case classes; +SetHash * Cartesian[Hash] * ContravariantCartesian[Hash] * ContravariantCartesian[Hash]; Hash.fromHashing * style issues * remove unused import * some test cases * some test cases * +test for Contravariant[Hash] * +test for Contravariant[Hash] * Fix duplication error and style error * fix merge error * remove instance for Cartesian[Hash]: it does not satisfy associativity * +identityHash, +`hash` postfix method * all tests passed * increase coverage * accidentally removed plugin, restore it * all tests passed * increase coverage * increase coverage, ## => hashCode, kernelBoiler * suppress mimaReportBinaryIssues * Remove cats.functor * Remove cats.functor --- build.sbt | 118 ++++++++++++++- core/src/main/scala/cats/instances/all.scala | 1 + core/src/main/scala/cats/instances/hash.scala | 16 ++ core/src/main/scala/cats/package.scala | 2 + core/src/main/scala/cats/syntax/all.scala | 1 + core/src/main/scala/cats/syntax/hash.scala | 18 +++ .../scala/cats/kernel/laws/HashLaws.scala | 58 ++++++++ .../scala/cats/kernel/laws/LawTests.scala | 110 ++++++++++++-- kernel/src/main/scala/cats/kernel/Hash.scala | 59 ++++++++ .../cats/kernel/instances/StaticMethods.scala | 17 +++ .../scala/cats/kernel/instances/all.scala | 1 + .../cats/kernel/instances/bigDecimal.scala | 6 +- .../scala/cats/kernel/instances/bigInt.scala | 5 +- .../scala/cats/kernel/instances/bitSet.scala | 6 +- .../scala/cats/kernel/instances/boolean.scala | 6 +- .../scala/cats/kernel/instances/byte.scala | 6 +- .../scala/cats/kernel/instances/char.scala | 3 +- .../scala/cats/kernel/instances/double.scala | 7 +- .../scala/cats/kernel/instances/either.scala | 42 ++++-- .../scala/cats/kernel/instances/float.scala | 6 +- .../cats/kernel/instances/function.scala | 6 + .../scala/cats/kernel/instances/int.scala | 6 +- .../scala/cats/kernel/instances/list.scala | 22 +++ .../scala/cats/kernel/instances/long.scala | 6 +- .../scala/cats/kernel/instances/map.scala | 30 +++- .../scala/cats/kernel/instances/option.scala | 10 ++ .../scala/cats/kernel/instances/set.scala | 19 ++- .../scala/cats/kernel/instances/short.scala | 5 +- .../scala/cats/kernel/instances/stream.scala | 17 +++ .../scala/cats/kernel/instances/string.scala | 7 +- .../scala/cats/kernel/instances/symbol.scala | 6 +- .../scala/cats/kernel/instances/unit.scala | 6 +- .../scala/cats/kernel/instances/uuid.scala | 6 +- .../scala/cats/kernel/instances/vector.scala | 17 +++ .../cats/laws/discipline/Arbitrary.scala | 3 + .../main/scala/cats/laws/discipline/Eq.scala | 14 ++ project/KernelBoiler.scala | 140 ++++++++++++++++-- .../src/main/scala/cats/tests/Helpers.scala | 8 + .../test/scala/cats/tests/FunctionTests.scala | 4 +- .../src/test/scala/cats/tests/HashTests.scala | 16 ++ .../cats/tests/KernelContravariantTests.scala | 4 + 41 files changed, 759 insertions(+), 81 deletions(-) create mode 100644 core/src/main/scala/cats/instances/hash.scala create mode 100644 core/src/main/scala/cats/syntax/hash.scala create mode 100644 kernel-laws/src/main/scala/cats/kernel/laws/HashLaws.scala create mode 100644 kernel/src/main/scala/cats/kernel/Hash.scala create mode 100644 tests/src/test/scala/cats/tests/HashTests.scala diff --git a/build.sbt b/build.sbt index c4218a63c8..06f3581251 100644 --- a/build.sbt +++ b/build.sbt @@ -236,7 +236,123 @@ val binaryCompatibleVersion = "0.8.0" val binaryCompatibleExceptions = { import com.typesafe.tools.mima.core._ import com.typesafe.tools.mima.core.ProblemFilters._ - Seq( //todo: remove these once we release 1.0.0-RC1 + Seq( // todo: remove these once we release 1.0.0-RC1 + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances.*"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances1.*"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances2.*"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.DurationInstances.*"), + 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.TupleInstances.catsKernelStdEqForTuple12"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple7"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple4"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple13"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple11"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple1"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple7"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple16"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple22"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple1"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple14"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple4"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple20"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple19"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple10"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple17"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple18"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple3"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple9"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple6"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple3"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple12"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple22"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple6"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple19"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple10"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple9"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple21"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple15"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple13"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple16"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple20"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple14"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple5"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple2"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple8"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple17"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple5"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple15"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple21"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple11"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdPartialOrderForTuple2"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple8"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.TupleInstances.catsKernelStdEqForTuple18"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple9"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple16"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple22"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple3"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple6"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple21"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple18"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple12"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple15"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple8"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple2"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple5"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple20"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple14"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple17"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple4"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple11"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple7"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple1"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple10"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple19"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple13"), + exclude[ReversedMissingMethodProblem]("cats.kernel.instances.StreamInstances1.catsKernelStdHashForStream"), + exclude[ReversedMissingMethodProblem]("cats.kernel.instances.ListInstances1.catsKernelStdHashForList"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.SetInstances.catsKernelStdPartialOrderForSet"), + exclude[UpdateForwarderBodyProblem]("cats.kernel.instances.SetInstances.catsKernelStdSemilatticeForSet"), + exclude[ReversedMissingMethodProblem]("cats.kernel.instances.SetInstances.catsKernelStdHashForSet"), + exclude[ReversedMissingMethodProblem]("cats.kernel.instances.VectorInstances1.catsKernelStdHashForVector"), + exclude[DirectMissingMethodProblem]("cats.kernel.instances.BitSetInstances.catsKernelStdPartialOrderForBitSet"), + exclude[ReversedMissingMethodProblem]("cats.kernel.instances.BitSetInstances.cats$kernel$instances$BitSetInstances$_setter_$catsKernelStdOrderForBitSet_="), + exclude[ReversedMissingMethodProblem]("cats.kernel.instances.BitSetInstances.catsKernelStdOrderForBitSet"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple9"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple16"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple22"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple3"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple6"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple21"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple18"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple12"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple15"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple8"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple2"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple5"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple20"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple14"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple17"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple4"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple11"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple7"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple1"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple10"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple19"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.TupleInstances1.catsKernelStdHashForTuple13"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.EitherInstances1.catsStdEqForEither"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.EitherInstances.catsStdOrderForEither"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.EitherInstances.catsDataMonoidForEither"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.EitherInstances0.catsDataSemigroupForEither"), + exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.EitherInstances0.catsStdHashForEither"), + 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.EitherInstances0.catsStdHashForEither"), + 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[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances.*"), exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances1.*"), exclude[InheritedNewAbstractMethodProblem]("cats.kernel.instances.QueueInstances2.*"), diff --git a/core/src/main/scala/cats/instances/all.scala b/core/src/main/scala/cats/instances/all.scala index d974ee5d6c..a1204d4cc2 100644 --- a/core/src/main/scala/cats/instances/all.scala +++ b/core/src/main/scala/cats/instances/all.scala @@ -11,6 +11,7 @@ trait AllInstances with EquivInstances with FunctionInstances with FutureInstances + with HashInstances with ListInstances with MapInstances with MonoidInstances diff --git a/core/src/main/scala/cats/instances/hash.scala b/core/src/main/scala/cats/instances/hash.scala new file mode 100644 index 0000000000..443ef96e39 --- /dev/null +++ b/core/src/main/scala/cats/instances/hash.scala @@ -0,0 +1,16 @@ +package cats +package instances + + +trait HashInstances { + + implicit val catsContravariantForHash: Contravariant[Hash] = + new Contravariant[Hash] { + /** + * Derive a `Hash` for `B` given an `Hash[A]` and a function `B => A`. + */ + def contramap[A, B](ha: Hash[A])(f: B => A): Hash[B] = Hash.by(f)(ha) + + } + +} diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index 59fab09423..af9731e5d3 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -78,6 +78,7 @@ package object cats { type Eq[A] = cats.kernel.Eq[A] type PartialOrder[A] = cats.kernel.PartialOrder[A] type Order[A] = cats.kernel.Order[A] + type Hash[A] = cats.kernel.Hash[A] type Semigroup[A] = cats.kernel.Semigroup[A] type Monoid[A] = cats.kernel.Monoid[A] type Group[A] = cats.kernel.Group[A] @@ -85,6 +86,7 @@ package object cats { val Eq = cats.kernel.Eq val PartialOrder = cats.kernel.PartialOrder val Order = cats.kernel.Order + val Hash = cats.kernel.Hash val Semigroup = cats.kernel.Semigroup val Monoid = cats.kernel.Monoid val Group = cats.kernel.Group diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 2f6d9be450..04d099c21e 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -22,6 +22,7 @@ trait AllSyntax with FoldableSyntax with FunctorSyntax with GroupSyntax + with HashSyntax with InvariantSyntax with IorSyntax with ListSyntax diff --git a/core/src/main/scala/cats/syntax/hash.scala b/core/src/main/scala/cats/syntax/hash.scala new file mode 100644 index 0000000000..57adf774c4 --- /dev/null +++ b/core/src/main/scala/cats/syntax/hash.scala @@ -0,0 +1,18 @@ +package cats +package syntax + +import cats.macros.Ops + +trait HashSyntax { + + implicit def catsSyntaxHash[A: Hash](a: A): HashOps[A] = + new HashOps[A](a) + +} + +final class HashOps[A: Hash](a: A) { + /** + * Gets the hash code of this object given an implicit `Hash` instance. + */ + def hash: Int = macro Ops.unop0[Int] +} diff --git a/kernel-laws/src/main/scala/cats/kernel/laws/HashLaws.scala b/kernel-laws/src/main/scala/cats/kernel/laws/HashLaws.scala new file mode 100644 index 0000000000..dade828729 --- /dev/null +++ b/kernel-laws/src/main/scala/cats/kernel/laws/HashLaws.scala @@ -0,0 +1,58 @@ +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]] + } +} + +/** + * @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, + Rules.serializable(Equ), + "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, + Rules.serializable(Equ), + "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, + Rules.serializable(Equ), + "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: _*) + +} 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 3d0a37f138..27438cc10a 100644 --- a/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala +++ b/kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala @@ -21,7 +21,19 @@ import scala.util.Random import java.util.UUID import java.util.concurrent.TimeUnit.{DAYS, HOURS, MINUTES, SECONDS, MILLISECONDS, MICROSECONDS, NANOSECONDS} -object KernelCheck { +class LawTests extends FunSuite with Discipline { + + // The scalacheck defaults (100,100) are too high for scala-js. + final val PropMaxSize: PosZInt = if (Platform.isJs) 10 else 100 + final val PropMinSuccessful: PosInt = if (Platform.isJs) 10 else 100 + + implicit override val generatorDrivenConfig: PropertyCheckConfiguration = + PropertyCheckConfiguration(minSuccessful = PropMinSuccessful, sizeRange = PropMaxSize) + + implicit def hashLaws[A: Cogen: Eq: Arbitrary]: HashLaws[A] = HashLaws[A] + + implicit def orderLaws[A: Cogen: Eq: Arbitrary]: OrderLaws[A] = OrderLaws[A] + implicit def groupLaws[A: Cogen: Eq: Arbitrary]: GroupLaws[A] = GroupLaws[A] implicit val arbitraryBitSet: Arbitrary[BitSet] = Arbitrary(arbitrary[List[Short]].map(ns => BitSet(ns.map(_ & 0xffff): _*))) @@ -77,21 +89,6 @@ object KernelCheck { case NANOSECONDS => 6128745701389500153L }) } -} - -class LawTests extends FunSuite with Discipline { - - import KernelCheck._ - - // The scalacheck defaults (100,100) are too high for scala-js. - final val PropMaxSize: PosZInt = if (Platform.isJs) 10 else 100 - final val PropMinSuccessful: PosInt = if (Platform.isJs) 10 else 100 - - implicit override val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfiguration(minSuccessful = PropMinSuccessful, sizeRange = PropMaxSize) - - implicit def orderLaws[A: Cogen: Eq: Arbitrary]: OrderLaws[A] = OrderLaws[A] - implicit def groupLaws[A: Cogen: Eq: Arbitrary]: GroupLaws[A] = GroupLaws[A] { // needed for Cogen[Map[...]] @@ -99,6 +96,76 @@ class LawTests extends FunSuite with Discipline { laws[OrderLaws, Map[String, HasEq[Int]]].check(_.eqv) } + 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) + + // 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) + laws[OrderLaws, List[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Option[HasEq[Int]]].check(_.eqv) laws[OrderLaws, Vector[HasEq[Int]]].check(_.eqv) @@ -308,6 +375,17 @@ class LawTests extends FunSuite with Discipline { Cogen[A].contramap(_.a) } + case class HasHash[A](a: A) + + object HasHash { + implicit def hasHash[A: Hash]: Hash[HasHash[A]] = + Hash.by(_.a) // not Hash[A].on(_.a) because of diamond inheritance problems with Eq + implicit def hasHashArbitrary[A: Arbitrary]: Arbitrary[HasHash[A]] = + Arbitrary(arbitrary[A].map(HasHash(_))) + implicit def hasCogen[A: Cogen]: Cogen[HasHash[A]] = + Cogen[A].contramap(_.a) + } + case class LawChecker[L <: Laws](name: String, laws: L) { def check(f: L => L#RuleSet): Unit = checkAll(name, f(laws)) } diff --git a/kernel/src/main/scala/cats/kernel/Hash.scala b/kernel/src/main/scala/cats/kernel/Hash.scala new file mode 100644 index 0000000000..19db76bfa8 --- /dev/null +++ b/kernel/src/main/scala/cats/kernel/Hash.scala @@ -0,0 +1,59 @@ +package cats.kernel + +import scala.{specialized => sp} +import scala.util.hashing.Hashing + +/** + * A type class used to represent a hashing scheme for objects of a given type. + * For any two instances `x` and `y` that are considered equivalent under the + * equivalence relation defined by this object, `hash(x)` should equal `hash(y)`. + * @author Tongfei Chen + */ +trait Hash[@sp A] extends Any with Eq[A] with Serializable { self => + + /** + * Returns the hash code of the given object under this hashing scheme. + */ + def hash(x: A): Int + + // `Hash#on` deliberately not implemented to avoid `Hash`/`Order` diamond inheritance problem. + // Please use `Hash.by` for the same functionality. + + // `Hash#toHashing` deliberately not implemented since `scala.util.hashing.Hashing` is only + // compatible with universal equality. +} + +abstract class HashFunctions[H[T] <: Hash[T]] extends EqFunctions[H] { + + def hash[@sp A](x: A)(implicit ev: H[A]): Int = ev.hash(x) + +} + + +object Hash extends HashFunctions[Hash] { + + /** Fetch a `Hash` instance given the specific type. */ + @inline final def apply[A](implicit ev: Hash[A]): Hash[A] = ev + + def by[@sp A, @sp B](f: A => B)(implicit ev: Hash[B]): Hash[A] = + new Hash[A] { + def hash(x: A) = ev.hash(f(x)) + def eqv(x: A, y: A) = ev.eqv(f(x), f(y)) + } + + def fromHashing[A](implicit ev: Hashing[A]): Hash[A] = + new Hash[A] { + def hash(x: A) = ev.hash(x) + def eqv(x: A, y: A) = x == y // universal equality + } + + /** + * Constructs a `Hash` instance by using the universal `hashCode` function and the universal equality relation. + */ + def fromUniversalHashCode[A]: Hash[A] = + new Hash[A] { + def hash(x: A) = x.hashCode() + def eqv(x: A, y: A) = x == y + } + +} diff --git a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala index 7aca9f6170..85944bc1ea 100644 --- a/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala +++ b/kernel/src/main/scala/cats/kernel/instances/StaticMethods.scala @@ -79,4 +79,21 @@ object StaticMethods { xs.foreach(b ++= _) b.result } + + // Adapted from scala.util.hashing.MurmurHash#productHash. + def product1Hash(_1Hash: Int): Int = { + import scala.util.hashing.MurmurHash3._ + var h = productSeed + h = mix(h, _1Hash) + finalizeHash(h, 1) + } + + // Adapted from scala.util.hashing.MurmurHash#productHash. + def product2Hash(_1Hash: Int, _2Hash: Int): Int = { + import scala.util.hashing.MurmurHash3._ + var h = productSeed + h = mix(h, _1Hash) + h = mix(h, _2Hash) + finalizeHash(h, 2) + } } diff --git a/kernel/src/main/scala/cats/kernel/instances/all.scala b/kernel/src/main/scala/cats/kernel/instances/all.scala index 269a34d9ff..954431c68a 100644 --- a/kernel/src/main/scala/cats/kernel/instances/all.scala +++ b/kernel/src/main/scala/cats/kernel/instances/all.scala @@ -11,6 +11,7 @@ trait AllInstances with ByteInstances with CharInstances with DoubleInstances + with EitherInstances with DurationInstances with FloatInstances with FunctionInstances diff --git a/kernel/src/main/scala/cats/kernel/instances/bigDecimal.scala b/kernel/src/main/scala/cats/kernel/instances/bigDecimal.scala index 28203cb91e..910eb8e147 100644 --- a/kernel/src/main/scala/cats/kernel/instances/bigDecimal.scala +++ b/kernel/src/main/scala/cats/kernel/instances/bigDecimal.scala @@ -4,7 +4,7 @@ package instances package object bigDecimal extends BigDecimalInstances // scalastyle:ignore package.object.name trait BigDecimalInstances { - implicit val catsKernelStdOrderForBigDecimal: Order[BigDecimal] = + implicit val catsKernelStdOrderForBigDecimal: Order[BigDecimal] with Hash[BigDecimal] = new BigDecimalOrder implicit val catsKernelStdGroupForBigDecimal: CommutativeGroup[BigDecimal] = new BigDecimalGroup @@ -17,7 +17,9 @@ class BigDecimalGroup extends CommutativeGroup[BigDecimal] { override def remove(x: BigDecimal, y: BigDecimal): BigDecimal = x - y } -class BigDecimalOrder extends Order[BigDecimal] { +class BigDecimalOrder extends Order[BigDecimal] with Hash[BigDecimal] { + + def hash(x: BigDecimal): Int = x.hashCode() def compare(x: BigDecimal, y: BigDecimal): Int = x compare y diff --git a/kernel/src/main/scala/cats/kernel/instances/bigInt.scala b/kernel/src/main/scala/cats/kernel/instances/bigInt.scala index 0cf236c3a9..46a5a26af8 100644 --- a/kernel/src/main/scala/cats/kernel/instances/bigInt.scala +++ b/kernel/src/main/scala/cats/kernel/instances/bigInt.scala @@ -4,7 +4,7 @@ package instances package object bigInt extends BigIntInstances // scalastyle:ignore package.object.name trait BigIntInstances { - implicit val catsKernelStdOrderForBigInt: Order[BigInt] = + implicit val catsKernelStdOrderForBigInt: Order[BigInt] with Hash[BigInt] = new BigIntOrder implicit val catsKernelStdGroupForBigInt: CommutativeGroup[BigInt] = new BigIntGroup @@ -17,8 +17,9 @@ class BigIntGroup extends CommutativeGroup[BigInt] { override def remove(x: BigInt, y: BigInt): BigInt = x - y } -class BigIntOrder extends Order[BigInt] { +class BigIntOrder extends Order[BigInt] with Hash[BigInt] { + def hash(x: BigInt): Int = x.hashCode() def compare(x: BigInt, y: BigInt): Int = x compare y override def eqv(x: BigInt, y: BigInt): Boolean = x == y diff --git a/kernel/src/main/scala/cats/kernel/instances/bitSet.scala b/kernel/src/main/scala/cats/kernel/instances/bitSet.scala index 52ca9f2a01..8846d2071e 100644 --- a/kernel/src/main/scala/cats/kernel/instances/bitSet.scala +++ b/kernel/src/main/scala/cats/kernel/instances/bitSet.scala @@ -6,14 +6,16 @@ import scala.collection.immutable.BitSet package object bitSet extends BitSetInstances // scalastyle:ignore package.object.name trait BitSetInstances { - implicit val catsKernelStdPartialOrderForBitSet: PartialOrder[BitSet] = + implicit val catsKernelStdOrderForBitSet: PartialOrder[BitSet] with Hash[BitSet] = new BitSetPartialOrder implicit val catsKernelStdSemilatticeForBitSet: BoundedSemilattice[BitSet] = new BitSetSemilattice } -class BitSetPartialOrder extends PartialOrder[BitSet] { +class BitSetPartialOrder extends PartialOrder[BitSet] with Hash[BitSet] { + def hash(x: BitSet): Int = x.hashCode() + def partialCompare(x: BitSet, y: BitSet): Double = if (x eq y) 0.0 else if (x.size < y.size) if (x.subsetOf(y)) -1.0 else Double.NaN diff --git a/kernel/src/main/scala/cats/kernel/instances/boolean.scala b/kernel/src/main/scala/cats/kernel/instances/boolean.scala index be5ba31c7e..8b00558796 100644 --- a/kernel/src/main/scala/cats/kernel/instances/boolean.scala +++ b/kernel/src/main/scala/cats/kernel/instances/boolean.scala @@ -4,11 +4,13 @@ package instances package object boolean extends BooleanInstances trait BooleanInstances { - implicit val catsKernelStdOrderForBoolean: Order[Boolean] = + implicit val catsKernelStdOrderForBoolean: Order[Boolean] with Hash[Boolean] = new BooleanOrder } -class BooleanOrder extends Order[Boolean] { +class BooleanOrder extends Order[Boolean] with Hash[Boolean] { + + def hash(x: Boolean): Int = x.hashCode() def compare(x: Boolean, y: Boolean): Int = if (x == y) 0 else if (x) 1 else -1 diff --git a/kernel/src/main/scala/cats/kernel/instances/byte.scala b/kernel/src/main/scala/cats/kernel/instances/byte.scala index f50c0895ff..8c9c46bf5d 100644 --- a/kernel/src/main/scala/cats/kernel/instances/byte.scala +++ b/kernel/src/main/scala/cats/kernel/instances/byte.scala @@ -4,7 +4,7 @@ package instances package object byte extends ByteInstances trait ByteInstances { - implicit val catsKernelStdOrderForByte: Order[Byte] = new ByteOrder + implicit val catsKernelStdOrderForByte: Order[Byte] with Hash[Byte] = new ByteOrder implicit val catsKernelStdGroupForByte: CommutativeGroup[Byte] = new ByteGroup } @@ -15,7 +15,9 @@ class ByteGroup extends CommutativeGroup[Byte] { override def remove(x: Byte, y: Byte): Byte = (x - y).toByte } -class ByteOrder extends Order[Byte] { +class ByteOrder extends Order[Byte] with Hash[Byte] { + + def hash(x: Byte): Int = x.hashCode() def compare(x: Byte, y: Byte): Int = if (x < y) -1 else if (x > y) 1 else 0 diff --git a/kernel/src/main/scala/cats/kernel/instances/char.scala b/kernel/src/main/scala/cats/kernel/instances/char.scala index 4238fa84ad..be0a77949c 100644 --- a/kernel/src/main/scala/cats/kernel/instances/char.scala +++ b/kernel/src/main/scala/cats/kernel/instances/char.scala @@ -7,7 +7,8 @@ trait CharInstances { implicit val catsKernelStdOrderForChar = new CharOrder } -class CharOrder extends Order[Char] { +class CharOrder extends Order[Char] with Hash[Char] { + def hash(x: Char): Int = x.hashCode() def compare(x: Char, y: Char): Int = if (x < y) -1 else if (x > y) 1 else 0 override def eqv(x:Char, y:Char): Boolean = x == y diff --git a/kernel/src/main/scala/cats/kernel/instances/double.scala b/kernel/src/main/scala/cats/kernel/instances/double.scala index b4a6527fa9..e9f8c203ec 100644 --- a/kernel/src/main/scala/cats/kernel/instances/double.scala +++ b/kernel/src/main/scala/cats/kernel/instances/double.scala @@ -1,12 +1,10 @@ package cats.kernel package instances -import java.lang.Math - package object double extends DoubleInstances trait DoubleInstances { - implicit val catsKernelStdOrderForDouble: Order[Double] = new DoubleOrder + implicit val catsKernelStdOrderForDouble: Order[Double] with Hash[Double] = new DoubleOrder implicit val catsKernelStdGroupForDouble: CommutativeGroup[Double] = new DoubleGroup } @@ -17,8 +15,9 @@ class DoubleGroup extends CommutativeGroup[Double] { override def remove(x: Double, y: Double): Double = x - y } -class DoubleOrder extends Order[Double] { +class DoubleOrder extends Order[Double] with Hash[Double] { + def hash(x: Double): Int = x.hashCode() def compare(x: Double, y: Double): Int = java.lang.Double.compare(x, y) diff --git a/kernel/src/main/scala/cats/kernel/instances/either.scala b/kernel/src/main/scala/cats/kernel/instances/either.scala index afe7f8f57d..bf71f23389 100644 --- a/kernel/src/main/scala/cats/kernel/instances/either.scala +++ b/kernel/src/main/scala/cats/kernel/instances/either.scala @@ -63,21 +63,37 @@ trait EitherInstances0 extends EitherInstances1 { } } } + + implicit def catsStdHashForEither[A, B](implicit A: Hash[A], B: Hash[B]): Hash[Either[A, B]] = new EitherHash[A, B] } trait EitherInstances1 { - implicit def catsStdEqForEither[A, B](implicit A: Eq[A], B: Eq[B]): Eq[Either[A, B]] = - new Eq[Either[A, B]] { - def eqv(x: Either[A, B], y: Either[A, B]): Boolean = - x match { - case Left(xx) => y match { - case Left(yy) => A.eqv(xx, yy) - case Right(_) => false - } - case Right(xx) => y match { - case Left(_) => false - case Right(yy) => B.eqv(xx, yy) - } - } + + implicit def catsStdEqForEither[A, B](implicit A: Eq[A], B: Eq[B]): Eq[Either[A, B]] = new EitherEq[A, B] + +} + + +// isolated class for inheritance +class EitherEq[A, B](implicit A: Eq[A], B: Eq[B]) extends Eq[Either[A, B]] { + def eqv(x: Either[A, B], y: Either[A, B]): Boolean = + x match { + case Left(xx) => y match { + case Left(yy) => A.eqv(xx, yy) + case Right(_) => false + } + case Right(xx) => y match { + case Left(_) => false + case Right(yy) => B.eqv(xx, yy) + } + } +} + +class EitherHash[A, B](implicit A: Hash[A], B: Hash[B]) extends EitherEq[A, B] with Hash[Either[A, B]] { + def hash(x: Either[A, B]): Int = { + x match { + case Left(xx) => StaticMethods.product1Hash(A.hash(xx)) + case Right(xx) => StaticMethods.product1Hash(B.hash(xx)) } + } } diff --git a/kernel/src/main/scala/cats/kernel/instances/float.scala b/kernel/src/main/scala/cats/kernel/instances/float.scala index b1c0e15b76..d6b9a07e39 100644 --- a/kernel/src/main/scala/cats/kernel/instances/float.scala +++ b/kernel/src/main/scala/cats/kernel/instances/float.scala @@ -4,7 +4,7 @@ package instances package object float extends FloatInstances trait FloatInstances { - implicit val catsKernelStdOrderForFloat: Order[Float] = new FloatOrder + implicit val catsKernelStdOrderForFloat: Order[Float] with Hash[Float] = new FloatOrder implicit val catsKernelStdGroupForFloat: CommutativeGroup[Float] = new FloatGroup } @@ -26,7 +26,9 @@ class FloatGroup extends CommutativeGroup[Float] { * If you would prefer an absolutely lawful fractional value, you'll * need to investigate rational numbers or more exotic types. */ -class FloatOrder extends Order[Float] { +class FloatOrder extends Order[Float] with Hash[Float] { + + def hash(x: Float): Int = x.hashCode() def compare(x: Float, y: Float): Int = java.lang.Float.compare(x, y) diff --git a/kernel/src/main/scala/cats/kernel/instances/function.scala b/kernel/src/main/scala/cats/kernel/instances/function.scala index 97a00ed9ab..4ff4f1476e 100644 --- a/kernel/src/main/scala/cats/kernel/instances/function.scala +++ b/kernel/src/main/scala/cats/kernel/instances/function.scala @@ -19,6 +19,12 @@ trait FunctionInstances extends FunctionInstances0 { trait FunctionInstances0 extends FunctionInstances1 { + implicit def catsKernelHashForFunction0[A](implicit ev: Hash[A]): Hash[() => A] = + new Hash[() => A] { + def hash(x: () => A) = ev.hash(x()) + def eqv(x: () => A, y: () => A) = ev.eqv(x(), y()) + } + implicit def catsKernelPartialOrderForFunction0[A](implicit ev: PartialOrder[A]): PartialOrder[() => A] = new PartialOrder[() => A] { def partialCompare(x: () => A, y: () => A): Double = ev.partialCompare(x(), y()) diff --git a/kernel/src/main/scala/cats/kernel/instances/int.scala b/kernel/src/main/scala/cats/kernel/instances/int.scala index 65ffe8ae1e..3d8e8639dd 100644 --- a/kernel/src/main/scala/cats/kernel/instances/int.scala +++ b/kernel/src/main/scala/cats/kernel/instances/int.scala @@ -4,7 +4,7 @@ package instances package object int extends IntInstances trait IntInstances { - implicit val catsKernelStdOrderForInt: Order[Int] = new IntOrder + implicit val catsKernelStdOrderForInt: Order[Int] with Hash[Int] = new IntOrder implicit val catsKernelStdGroupForInt: CommutativeGroup[Int] = new IntGroup } @@ -15,8 +15,8 @@ class IntGroup extends CommutativeGroup[Int] { override def remove(x: Int, y: Int): Int = x - y } -class IntOrder extends Order[Int] { - +class IntOrder extends Order[Int] with Hash[Int] { + def hash(x: Int): Int = x.hashCode() def compare(x: Int, y: Int): Int = if (x < y) -1 else if (x > y) 1 else 0 diff --git a/kernel/src/main/scala/cats/kernel/instances/list.scala b/kernel/src/main/scala/cats/kernel/instances/list.scala index 9f19482289..afdf3c97d4 100644 --- a/kernel/src/main/scala/cats/kernel/instances/list.scala +++ b/kernel/src/main/scala/cats/kernel/instances/list.scala @@ -15,6 +15,9 @@ trait ListInstances extends ListInstances1 { trait ListInstances1 extends ListInstances2 { implicit def catsKernelStdPartialOrderForList[A: PartialOrder]: PartialOrder[List[A]] = new ListPartialOrder[A] + + implicit def catsKernelStdHashForList[A: Hash]: Hash[List[A]] = + new ListHash[A] } trait ListInstances2 { @@ -58,6 +61,25 @@ 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) + } +} + class ListEq[A](implicit ev: Eq[A]) extends Eq[List[A]] { def eqv(xs: List[A], ys: List[A]): Boolean = { def loop(xs: List[A], ys: List[A]): Boolean = diff --git a/kernel/src/main/scala/cats/kernel/instances/long.scala b/kernel/src/main/scala/cats/kernel/instances/long.scala index 1a4b2dfc7d..98fb2ecd7f 100644 --- a/kernel/src/main/scala/cats/kernel/instances/long.scala +++ b/kernel/src/main/scala/cats/kernel/instances/long.scala @@ -4,7 +4,7 @@ package instances package object long extends LongInstances trait LongInstances { - implicit val catsKernelStdOrderForLong: Order[Long] = new LongOrder + implicit val catsKernelStdOrderForLong: Order[Long] with Hash[Long] = new LongOrder implicit val catsKernelStdGroupForLong: CommutativeGroup[Long] = new LongGroup } @@ -15,7 +15,9 @@ class LongGroup extends CommutativeGroup[Long] { override def remove(x: Long, y: Long): Long = x - y } -class LongOrder extends Order[Long] { +class LongOrder extends Order[Long] with Hash[Long] { + + def hash(x: Long): Int = x.hashCode() // use java.lang.Long.compare if we can rely on java >= 1.7 def compare(x: Long, y: Long): Int = diff --git a/kernel/src/main/scala/cats/kernel/instances/map.scala b/kernel/src/main/scala/cats/kernel/instances/map.scala index 1a1e0fd7e9..668d5243c8 100644 --- a/kernel/src/main/scala/cats/kernel/instances/map.scala +++ b/kernel/src/main/scala/cats/kernel/instances/map.scala @@ -5,13 +5,41 @@ import scala.collection.mutable package object map extends MapInstances -trait MapInstances { +trait MapInstances extends MapInstances1 { + implicit def catsKernelStdHashForMap[K: Hash, V: Hash]: Hash[Map[K, V]] = + new MapHash[K, V] +} + +trait MapInstances1 { implicit def catsKernelStdEqForMap[K, V: Eq]: Eq[Map[K, V]] = new MapEq[K, V] implicit def catsKernelStdMonoidForMap[K, V: Semigroup]: Monoid[Map[K, V]] = new MapMonoid[K, V] } +class MapHash[K, V](implicit V: Hash[V]) extends MapEq[K, V]()(V) with Hash[Map[K, V]] { + // adapted from [[scala.util.hashing.MurmurHash3]], + // but modified standard `Any#hashCode` to `ev.hash`. + import scala.util.hashing.MurmurHash3._ + def hash(x: Map[K, V]): Int = { + var a, b, n = 0 + var c = 1; + x foreach { case (k, v) => + // use the default hash on keys because that's what Scala's Map does + val h = StaticMethods.product2Hash(k.hashCode(), V.hash(v)) + a += h + b ^= h + if (h != 0) c *= h + n += 1 + } + var h = mapSeed + h = mix(h, a) + h = mix(h, b) + h = mixLast(h, c) + finalizeHash(h, n) + } +} + class MapEq[K, V](implicit V: Eq[V]) extends Eq[Map[K, V]] { def eqv(x: Map[K, V], y: Map[K, V]): Boolean = if (x eq y) true diff --git a/kernel/src/main/scala/cats/kernel/instances/option.scala b/kernel/src/main/scala/cats/kernel/instances/option.scala index 01e0f10613..e0a497ad9e 100644 --- a/kernel/src/main/scala/cats/kernel/instances/option.scala +++ b/kernel/src/main/scala/cats/kernel/instances/option.scala @@ -13,6 +13,9 @@ trait OptionInstances extends OptionInstances1 { trait OptionInstances1 extends OptionInstances0 { implicit def catsKernelStdPartialOrderForOption[A: PartialOrder]: PartialOrder[Option[A]] = new OptionPartialOrder[A] + + implicit def catsKernelStdHashForOption[A: Hash]: Hash[Option[A]] = + new OptionHash[A] } trait OptionInstances0 { @@ -46,6 +49,13 @@ class OptionPartialOrder[A](implicit A: PartialOrder[A]) extends PartialOrder[Op } } +class OptionHash[A](implicit A: Hash[A]) extends OptionEq[A]()(A) with Hash[Option[A]] { + def hash(x: Option[A]): Int = x match { + case None => None.hashCode() + case Some(xx) => StaticMethods.product1Hash(A.hash(xx)) + } +} + class OptionEq[A](implicit A: Eq[A]) extends Eq[Option[A]] { def eqv(x: Option[A], y: Option[A]): Boolean = x match { diff --git a/kernel/src/main/scala/cats/kernel/instances/set.scala b/kernel/src/main/scala/cats/kernel/instances/set.scala index 735f3edd1a..793eea642b 100644 --- a/kernel/src/main/scala/cats/kernel/instances/set.scala +++ b/kernel/src/main/scala/cats/kernel/instances/set.scala @@ -3,7 +3,12 @@ package instances package object set extends SetInstances -trait SetInstances { +trait SetInstances extends SetInstances1 { + implicit def catsKernelStdHashForSet[A]: Hash[Set[A]] = + new SetHash[A] +} + +trait SetInstances1 { implicit def catsKernelStdPartialOrderForSet[A]: PartialOrder[Set[A]] = new SetPartialOrder[A] @@ -19,8 +24,16 @@ class SetPartialOrder[A] extends PartialOrder[Set[A]] { else if (x == y) 0.0 else Double.NaN - override def eqv(x: Set[A], y: Set[A]): Boolean = - x == y + // Does not require an Eq on elements: Scala sets must use the universal `equals`. + override def eqv(x: Set[A], y: Set[A]): Boolean = x == y +} + +class SetHash[A] extends Hash[Set[A]] { + // Does not require a Hash on elements: Scala sets must use the universal `hashCode`. + def hash(x: Set[A]): Int = x.hashCode() + + // Does not require an Eq on elements: Scala sets must use the universal `equals`. + def eqv(x: Set[A], y: Set[A]): Boolean = x == y } class SetSemilattice[A] extends BoundedSemilattice[Set[A]] { diff --git a/kernel/src/main/scala/cats/kernel/instances/short.scala b/kernel/src/main/scala/cats/kernel/instances/short.scala index 06f56b028f..99328f0578 100644 --- a/kernel/src/main/scala/cats/kernel/instances/short.scala +++ b/kernel/src/main/scala/cats/kernel/instances/short.scala @@ -4,7 +4,7 @@ package instances package object short extends ShortInstances trait ShortInstances { - implicit val catsKernelStdOrderForShort: Order[Short] = new ShortOrder + implicit val catsKernelStdOrderForShort: Order[Short] with Hash[Short] = new ShortOrder implicit val catsKernelStdGroupForShort: CommutativeGroup[Short] = new ShortGroup } @@ -15,8 +15,9 @@ class ShortGroup extends CommutativeGroup[Short] { override def remove(x: Short, y: Short): Short = (x - y).toShort } -class ShortOrder extends Order[Short] { +class ShortOrder extends Order[Short] with Hash[Short] { + def hash(x: Short): Int = x.hashCode() // use java.lang.Short.compare if we can rely on java >= 1.7 def compare(x: Short, y: Short): Int = if (x < y) -1 else if (x > y) 1 else 0 diff --git a/kernel/src/main/scala/cats/kernel/instances/stream.scala b/kernel/src/main/scala/cats/kernel/instances/stream.scala index 8561997b94..3d001b43ba 100644 --- a/kernel/src/main/scala/cats/kernel/instances/stream.scala +++ b/kernel/src/main/scala/cats/kernel/instances/stream.scala @@ -13,6 +13,9 @@ trait StreamInstances extends StreamInstances1 { trait StreamInstances1 extends StreamInstances2 { implicit def catsKernelStdPartialOrderForStream[A: PartialOrder]: PartialOrder[Stream[A]] = new StreamPartialOrder[A] + + implicit def catsKernelStdHashForStream[A: Hash]: Hash[Stream[A]] = + new StreamHash[A] } trait StreamInstances2 { @@ -32,6 +35,20 @@ class StreamPartialOrder[A](implicit ev: PartialOrder[A]) extends PartialOrder[S else StaticMethods.iteratorPartialCompare(xs.iterator, ys.iterator) } +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) + } +} + class StreamEq[A](implicit ev: Eq[A]) extends Eq[Stream[A]] { def eqv(xs: Stream[A], ys: Stream[A]): Boolean = if (xs eq ys) true diff --git a/kernel/src/main/scala/cats/kernel/instances/string.scala b/kernel/src/main/scala/cats/kernel/instances/string.scala index be4de1fd62..14faf231e8 100644 --- a/kernel/src/main/scala/cats/kernel/instances/string.scala +++ b/kernel/src/main/scala/cats/kernel/instances/string.scala @@ -4,11 +4,14 @@ package instances package object string extends StringInstances trait StringInstances { - implicit val catsKernelStdOrderForString: Order[String] = new StringOrder + implicit val catsKernelStdOrderForString: Order[String] with Hash[String] = new StringOrder implicit val catsKernelStdMonoidForString: Monoid[String] = new StringMonoid } -class StringOrder extends Order[String] { +class StringOrder extends Order[String] with Hash[String] { + + def hash(x: String): Int = x.hashCode() + override def eqv(x: String, y: String): Boolean = x == y def compare(x: String, y: String): Int = diff --git a/kernel/src/main/scala/cats/kernel/instances/symbol.scala b/kernel/src/main/scala/cats/kernel/instances/symbol.scala index b1261ba875..0b89e4fff2 100644 --- a/kernel/src/main/scala/cats/kernel/instances/symbol.scala +++ b/kernel/src/main/scala/cats/kernel/instances/symbol.scala @@ -4,10 +4,12 @@ package instances package object symbol extends SymbolInstances trait SymbolInstances { - implicit val catsKernelStdOrderForSymbol: Order[Symbol] = new SymbolOrder + implicit val catsKernelStdOrderForSymbol: Order[Symbol] with Hash[Symbol] = new SymbolOrder } -class SymbolOrder extends Order[Symbol] { +class SymbolOrder extends Order[Symbol] with Hash[Symbol] { + + def hash(x: Symbol): Int = x.hashCode() override def eqv(x: Symbol, y: Symbol): Boolean = { // Symbols are interned diff --git a/kernel/src/main/scala/cats/kernel/instances/unit.scala b/kernel/src/main/scala/cats/kernel/instances/unit.scala index 068928cfce..ede615bbc3 100644 --- a/kernel/src/main/scala/cats/kernel/instances/unit.scala +++ b/kernel/src/main/scala/cats/kernel/instances/unit.scala @@ -4,16 +4,18 @@ package instances package object unit extends UnitInstances trait UnitInstances { - implicit val catsKernelStdOrderForUnit: Order[Unit] = + implicit val catsKernelStdOrderForUnit: Order[Unit] with Hash[Unit] = new UnitOrder implicit val catsKernelStdAlgebraForUnit: BoundedSemilattice[Unit] with CommutativeGroup[Unit] = new UnitAlgebra } -class UnitOrder extends Order[Unit] { +class UnitOrder extends Order[Unit] with Hash[Unit] { def compare(x: Unit, y: Unit): Int = 0 + def hash(x: Unit): Int = 0 // ().hashCode() == 0 + override def eqv(x: Unit, y: Unit): Boolean = true override def neqv(x: Unit, y: Unit): Boolean = false override def gt(x: Unit, y: Unit): Boolean = false diff --git a/kernel/src/main/scala/cats/kernel/instances/uuid.scala b/kernel/src/main/scala/cats/kernel/instances/uuid.scala index 4501d23e5a..1ef98c6837 100644 --- a/kernel/src/main/scala/cats/kernel/instances/uuid.scala +++ b/kernel/src/main/scala/cats/kernel/instances/uuid.scala @@ -6,6 +6,8 @@ import java.util.UUID package object uuid extends UUIDInstances trait UUIDInstances { - implicit val catsKernelStdOrderForUUID: Order[UUID] = - Order.fromComparable[UUID] + implicit val catsKernelStdOrderForUUID: Order[UUID] with Hash[UUID] = new Order[UUID] with Hash[UUID] { + def compare(x: UUID, y: UUID): Int = x compareTo y + def hash(x: UUID): Int = x.hashCode() + } } diff --git a/kernel/src/main/scala/cats/kernel/instances/vector.scala b/kernel/src/main/scala/cats/kernel/instances/vector.scala index c0a191f2df..cc08e0fe6c 100644 --- a/kernel/src/main/scala/cats/kernel/instances/vector.scala +++ b/kernel/src/main/scala/cats/kernel/instances/vector.scala @@ -13,6 +13,9 @@ trait VectorInstances extends VectorInstances1 { trait VectorInstances1 extends VectorInstances2 { implicit def catsKernelStdPartialOrderForVector[A: PartialOrder]: PartialOrder[Vector[A]] = new VectorPartialOrder[A] + + implicit def catsKernelStdHashForVector[A: Hash]: Hash[Vector[A]] = + new VectorHash[A] } trait VectorInstances2 { @@ -32,6 +35,20 @@ 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 VectorEq[A](implicit ev: Eq[A]) extends Eq[Vector[A]] { def eqv(xs: Vector[A], ys: Vector[A]): Boolean = if (xs eq ys) true diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index ba14df7cd5..2de9d40bdf 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -162,6 +162,9 @@ object arbitrary extends ArbitraryInstances0 { implicit def catsLawsArbitraryForOrdering[A: Arbitrary]: Arbitrary[Ordering[A]] = Arbitrary(getArbitrary[Order[A]].map(Order.catsKernelOrderingForOrder(_))) + implicit def catsLawsArbitraryForHash[A: Hash]: Arbitrary[Hash[A]] = + Arbitrary(Hash.fromUniversalHashCode[A]) + implicit def catsLawsArbitraryForNested[F[_], G[_], A](implicit FG: Arbitrary[F[G[A]]]): Arbitrary[Nested[F, G, A]] = Arbitrary(FG.arbitrary.map(Nested(_))) diff --git a/laws/src/main/scala/cats/laws/discipline/Eq.scala b/laws/src/main/scala/cats/laws/discipline/Eq.scala index 8186c84743..70948ad28b 100644 --- a/laws/src/main/scala/cats/laws/discipline/Eq.scala +++ b/laws/src/main/scala/cats/laws/discipline/Eq.scala @@ -82,6 +82,20 @@ object eq { implicit def catsLawsEqForOrdering[A](implicit arbA: Arbitrary[(A, A)]): Eq[Ordering[A]] = sampledEq[Ordering[A], (A, A), Int](100) { case (p, (l, r)) => p.compare(l, r) } + /** + * Creates an approximation of Eq[Hash[A]] by generating 100 values for A + * and comparing the application of the two hash functions. + */ + implicit def catsLawsEqForHash[A](implicit arbA: Arbitrary[A]): Eq[Hash[A]] = new Eq[Hash[A]] { + def eqv(f: Hash[A], g: Hash[A]): Boolean = { + val samples = List.fill(100)(arbA.arbitrary.sample).collect { + case Some(a) => a + case None => sys.error("Could not generate arbitrary values to compare two Hash[A]") + } + samples.forall { x => f.hash(x) == g.hash(x) } + } + } + /** * Create an approximation of Eq[Semigroup[A]] by generating values for A * and comparing the application of the two combine functions. diff --git a/project/KernelBoiler.scala b/project/KernelBoiler.scala index c6c0f2fc8e..d47eafd7a2 100644 --- a/project/KernelBoiler.scala +++ b/project/KernelBoiler.scala @@ -22,7 +22,7 @@ object KernelBoiler { } val templates: Seq[Template] = Seq( - GenTupleInstances + GenTupleInstances, GenTupleInstances1, GenTupleInstances2 ) val header = "// auto-generated boilerplate" @@ -80,7 +80,7 @@ object KernelBoiler { object GenTupleInstances extends Template { override def range: IndexedSeq[Int] = 1 to maxArity - def filename(root: File): File = root / "cats" / "kernel" / "instances" / "TupleAlgebra.scala" + def filename(root: File): File = root / "cats" / "kernel" / "instances" / "TupleInstances.scala" def content(tv: TemplateVals): String = { import tv._ @@ -120,7 +120,7 @@ object KernelBoiler { |package cats.kernel |package instances | - |trait TupleInstances { + |trait TupleInstances extends TupleInstances1 { - implicit def catsKernelStdBandForTuple${arity}[${`A..N`}](implicit ${constraints("Band")}): Band[${`(A..N)`}] = - new Band[${`(A..N)`}] { - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} @@ -133,11 +133,6 @@ object KernelBoiler { - def inverse(x: ${`(A..N)`}): ${`(A..N)`} = ${unaryTuple("inverse")} - } - - - implicit def catsKernelStdEqForTuple${arity}[${`A..N`}](implicit ${constraints("Eq")}): Eq[${`(A..N)`}] = - - new Eq[${`(A..N)`}] { - - def eqv(x: ${`(A..N)`}, y: ${`(A..N)`}): Boolean = ${binMethod("eqv").mkString(" && ")} - - } - - - implicit def catsKernelStdMonoidForTuple${arity}[${`A..N`}](implicit ${constraints("Monoid")}): Monoid[${`(A..N)`}] = - new Monoid[${`(A..N)`}] { - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} @@ -150,12 +145,6 @@ object KernelBoiler { - ${binMethod("compare").mkString("Array(", ", ", ")")}.find(_ != 0).getOrElse(0) - } - - - implicit def catsKernelStdPartialOrderForTuple${arity}[${`A..N`}](implicit ${constraints("PartialOrder")}): PartialOrder[${`(A..N)`}] = - - new PartialOrder[${`(A..N)`}] { - - def partialCompare(x: ${`(A..N)`}, y: ${`(A..N)`}): Double = - - ${binMethod("partialCompare").mkString("Array(", ", ", ")")}.find(_ != 0.0).getOrElse(0.0) - - } - - - implicit def catsKernelStdSemigroupForTuple${arity}[${`A..N`}](implicit ${constraints("Semigroup")}): Semigroup[${`(A..N)`}] = - new Semigroup[${`(A..N)`}] { - def combine(x: ${`(A..N)`}, y: ${`(A..N)`}): ${`(A..N)`} = ${binTuple("combine")} @@ -169,4 +158,127 @@ object KernelBoiler { """ } } + + + object GenTupleInstances1 extends Template { + override def range: IndexedSeq[Int] = 1 to maxArity + + def filename(root: File): File = root / "cats" / "kernel" / "instances" / "TupleInstances1.scala" + + def content(tv: TemplateVals): String = { + import tv._ + + def constraints(constraint: String) = + synTypes.map(tpe => s"${tpe}: ${constraint}[${tpe}]").mkString(", ") + + def tuple(results: TraversableOnce[String]) = { + val resultsVec = results.toVector + val a = synTypes.size + val r = s"${0.until(a).map(i => resultsVec(i)).mkString(", ")}" + if (a == 1) "Tuple1(" ++ r ++ ")" + else s"(${r})" + } + + def tupleNHeader = s"Tuple${synTypes.size}" + + def binMethod(name: String) = + synTypes.zipWithIndex.iterator.map { + case (tpe, i) => + val j = i + 1 + s"${tpe}.${name}(x._${j}, y._${j})" + } + + def unaryMethod(name: String) = + synTypes.zipWithIndex.iterator.map { case (tpe, i) => + s"$tpe.$name(x._${i + 1})" + } + + def binTuple(name: String) = + tuple(binMethod(name)) + + def unaryTuple(name: String) = { + val m = synTypes.zipWithIndex.map { case (tpe, i) => s"${tpe}.${name}(x._${i + 1})" } + tuple(m) + } + + def nullaryTuple(name: String) = { + val m = synTypes.map(tpe => s"${tpe}.${name}") + tuple(m) + } + + block""" + |package cats.kernel + |package instances + | + |trait TupleInstances1 extends TupleInstances2 { + - implicit def catsKernelStdHashForTuple${arity}[${`A..N`}](implicit ${constraints("Hash")}): Hash[${`(A..N)`}] = + - new Hash[${`(A..N)`}] { + - def hash(x: ${`(A..N)`}): Int = ${unaryMethod("hash").mkString(s"$tupleNHeader(", ", ", ")")}.hashCode() + - def eqv(x: ${`(A..N)`}, y: ${`(A..N)`}): Boolean = ${binMethod("eqv").mkString(" && ")} + - } + - + - implicit def catsKernelStdPartialOrderForTuple${arity}[${`A..N`}](implicit ${constraints("PartialOrder")}): PartialOrder[${`(A..N)`}] = + - new PartialOrder[${`(A..N)`}] { + - def partialCompare(x: ${`(A..N)`}, y: ${`(A..N)`}): Double = + - ${binMethod("partialCompare").mkString("Array(", ", ", ")")}.find(_ != 0.0).getOrElse(0.0) + - } + |} + """ + } + } + + + object GenTupleInstances2 extends Template { + override def range: IndexedSeq[Int] = 1 to maxArity + + def filename(root: File): File = root / "cats" / "kernel" / "instances" / "TupleInstances2.scala" + + def content(tv: TemplateVals): String = { + import tv._ + + def constraints(constraint: String) = + synTypes.map(tpe => s"${tpe}: ${constraint}[${tpe}]").mkString(", ") + + def tuple(results: TraversableOnce[String]) = { + val resultsVec = results.toVector + val a = synTypes.size + val r = s"${0.until(a).map(i => resultsVec(i)).mkString(", ")}" + if (a == 1) "Tuple1(" ++ r ++ ")" + else s"(${r})" + } + + def binMethod(name: String) = + synTypes.zipWithIndex.iterator.map { + case (tpe, i) => + val j = i + 1 + s"${tpe}.${name}(x._${j}, y._${j})" + } + + def binTuple(name: String) = + tuple(binMethod(name)) + + def unaryTuple(name: String) = { + val m = synTypes.zipWithIndex.map { case (tpe, i) => s"${tpe}.${name}(x._${i + 1})" } + tuple(m) + } + + def nullaryTuple(name: String) = { + val m = synTypes.map(tpe => s"${tpe}.${name}") + tuple(m) + } + + block""" + |package cats.kernel + |package instances + | + |trait TupleInstances2 { + - implicit def catsKernelStdEqForTuple${arity}[${`A..N`}](implicit ${constraints("Eq")}): Eq[${`(A..N)`}] = + - new Eq[${`(A..N)`}] { + - def eqv(x: ${`(A..N)`}, y: ${`(A..N)`}): Boolean = ${binMethod("eqv").mkString(" && ")} + - } + |} + """ + } + } + } diff --git a/testkit/src/main/scala/cats/tests/Helpers.scala b/testkit/src/main/scala/cats/tests/Helpers.scala index 85cb8e7d00..6a222cde8f 100644 --- a/testkit/src/main/scala/cats/tests/Helpers.scala +++ b/testkit/src/main/scala/cats/tests/Helpers.scala @@ -59,6 +59,14 @@ object Helpers { } } + case class Hsh(n: Int) extends N + object Hsh extends Arb(new Hsh(_)) { + implicit object O extends Hash[Hsh] { + def hash(x: Hsh): Int = x.hashCode() + def eqv(x: Hsh, y: Hsh): Boolean = x.n == y.n + } + } + // Band case class Bnd(n: Int) extends N object Bnd extends Companion(new Bnd(_)) { diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 0788129a4b..5ca97373ac 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -6,10 +6,11 @@ import cats.arrow.{Choice, CommutativeArrow} import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ -import cats.kernel.laws.{GroupLaws, OrderLaws} +import cats.kernel.laws._ import cats.kernel.{CommutativeGroup, CommutativeMonoid, CommutativeSemigroup} import cats.kernel.{Band, BoundedSemilattice, Semilattice} + class FunctionTests extends CatsSuite { import Helpers._ @@ -44,6 +45,7 @@ class FunctionTests extends CatsSuite { checkAll("Function0[Eqed]", OrderLaws[Function0[Eqed]].eqv) checkAll("Function0[POrd]", OrderLaws[Function0[POrd]].partialOrder) checkAll("Function0[Ord]", OrderLaws[Function0[Ord]].order) + checkAll("Function0[Hsh]", HashLaws[Function0[Hsh]].hash) checkAll("Function0[Semi]", GroupLaws[Function0[Semi]].semigroup) checkAll("Function0[CSemi]", GroupLaws[Function0[CSemi]].commutativeSemigroup) checkAll("Function0[Bnd]", GroupLaws[Function0[Bnd]].band) diff --git a/tests/src/test/scala/cats/tests/HashTests.scala b/tests/src/test/scala/cats/tests/HashTests.scala new file mode 100644 index 0000000000..037122b2a6 --- /dev/null +++ b/tests/src/test/scala/cats/tests/HashTests.scala @@ -0,0 +1,16 @@ +package cats +package tests + + +class HashTests extends CatsSuite { + + { + Invariant[Hash] + Contravariant[Hash] + } + + assert(1.hash == 1.hashCode) + assert("ABC".hash == "ABC".hashCode) + + +} diff --git a/tests/src/test/scala/cats/tests/KernelContravariantTests.scala b/tests/src/test/scala/cats/tests/KernelContravariantTests.scala index 70d4c296f7..fa43e2db08 100644 --- a/tests/src/test/scala/cats/tests/KernelContravariantTests.scala +++ b/tests/src/test/scala/cats/tests/KernelContravariantTests.scala @@ -30,4 +30,8 @@ class KernelContravariantTests extends CatsSuite { checkAll("Contravariant[Order]", ContravariantTests[Order].contravariant[Int, Int, Int]) checkAll("Cartesian[Order]", CartesianTests[Order].cartesian[Int, Int, Int]) checkAll("Contravariant[Order]", SerializableTests.serializable(Contravariant[Order])) + + Contravariant[Hash] + checkAll("Contravariant[Hash]", ContravariantTests[Hash].contravariant[Int, Int, Int]) + checkAll("Contravariant[Hash]", SerializableTests.serializable(Contravariant[Hash])) }