Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hash typeclass #1712

Merged
merged 87 commits into from
Oct 4, 2017
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
9587384
cats.kernel.Hash and related instances (#1690)
ctongfei May 29, 2017
a1ef839
Hash laws
ctongfei May 29, 2017
b7fb209
all test passed
ctongfei May 29, 2017
04c1f96
Hash instances for tuples
ctongfei May 29, 2017
e6e6159
introduce implicit precedence in KernelBoiler: Order > PartialOrder >…
ctongfei May 29, 2017
66468e0
Add type alias in cats._; Add contravariant instance for `Hash`
ctongfei May 30, 2017
92f808c
HashFunctions extends EqFunctions
ctongfei May 30, 2017
69d4259
Merge branch 'master' into hash
ctongfei May 30, 2017
932903d
Move contravariant instances to separate trait, in accordance with (#…
ctongfei May 30, 2017
c8b3d5e
revert name change
ctongfei Jun 8, 2017
650e4f5
move EitherInstances1#EitherEq out
ctongfei Aug 29, 2017
bb043a6
Optimize hash computation on case classes without allocating a new ob…
ctongfei Aug 29, 2017
d087613
fixing the problem in CI build: method catsKernelStdOrderForChar()cat…
ctongfei Aug 29, 2017
a4f96b5
Full compliance with how Scala generates hash codes on case classes; …
ctongfei Aug 31, 2017
7edb0c5
Cartesian[Hash]
ctongfei Aug 31, 2017
8abefa0
ContravariantCartesian[Hash]
ctongfei Aug 31, 2017
703070b
ContravariantCartesian[Hash]; Hash.fromHashing
ctongfei Aug 31, 2017
b8ee98f
style issues
ctongfei Sep 1, 2017
5ebd13d
remove unused import
ctongfei Sep 1, 2017
8a950f8
some test cases
ctongfei Sep 5, 2017
0cd2b48
some test cases
ctongfei Sep 5, 2017
c3f6378
+test for Contravariant[Hash]
ctongfei Sep 7, 2017
48034eb
+test for Contravariant[Hash]
ctongfei Sep 7, 2017
987ab6d
Add NEL/NEV one (#1707)
peterneyens May 28, 2017
a432ace
move instances into separate trait (#1659)
yilinwei May 28, 2017
9f9fd1a
cats.kernel.Hash and related instances (#1690)
ctongfei May 29, 2017
ab539d1
Hash laws
ctongfei May 29, 2017
951561f
all test passed
ctongfei May 29, 2017
927a0c7
Hash instances for tuples
ctongfei May 29, 2017
0101cf2
introduce implicit precedence in KernelBoiler: Order > PartialOrder >…
ctongfei May 29, 2017
1005e3e
Add type alias in cats._; Add contravariant instance for `Hash`
ctongfei May 30, 2017
cf3218a
HashFunctions extends EqFunctions
ctongfei May 30, 2017
6ea5667
Move contravariant instances to separate trait, in accordance with (#…
ctongfei May 30, 2017
d4baf94
revert name change
ctongfei Jun 8, 2017
222fc29
move EitherInstances1#EitherEq out
ctongfei Aug 29, 2017
522b9ad
Optimize hash computation on case classes without allocating a new ob…
ctongfei Aug 29, 2017
fcb355c
fixing the problem in CI build: method catsKernelStdOrderForChar()cat…
ctongfei Aug 29, 2017
9397e2a
Full compliance with how Scala generates hash codes on case classes; …
ctongfei Aug 31, 2017
2cc9854
Cartesian[Hash]
ctongfei Aug 31, 2017
54ce4f8
ContravariantCartesian[Hash]
ctongfei Aug 31, 2017
37af90e
ContravariantCartesian[Hash]; Hash.fromHashing
ctongfei Aug 31, 2017
e114dc3
style issues
ctongfei Sep 1, 2017
52eceac
remove unused import
ctongfei Sep 1, 2017
737e2ec
some test cases
ctongfei Sep 5, 2017
4ddd79f
some test cases
ctongfei Sep 5, 2017
a7d3e3a
+test for Contravariant[Hash]
ctongfei Sep 7, 2017
1225a06
+test for Contravariant[Hash]
ctongfei Sep 7, 2017
517bd3c
rebase
ctongfei Sep 7, 2017
ece3413
cats.kernel.Hash and related instances (#1690)
ctongfei May 29, 2017
177edf5
Hash laws
ctongfei May 29, 2017
e170d22
all test passed
ctongfei May 29, 2017
916415c
Hash instances for tuples
ctongfei May 29, 2017
0dd4a12
introduce implicit precedence in KernelBoiler: Order > PartialOrder >…
ctongfei May 29, 2017
49fa9ae
Add type alias in cats._; Add contravariant instance for `Hash`
ctongfei May 30, 2017
bf96364
HashFunctions extends EqFunctions
ctongfei May 30, 2017
f645496
Move contravariant instances to separate trait, in accordance with (#…
ctongfei May 30, 2017
a59f9c7
revert name change
ctongfei Jun 8, 2017
79b7f1a
move EitherInstances1#EitherEq out
ctongfei Aug 29, 2017
4cedb1f
Optimize hash computation on case classes without allocating a new ob…
ctongfei Aug 29, 2017
d397983
fixing the problem in CI build: method catsKernelStdOrderForChar()cat…
ctongfei Aug 29, 2017
f772a7a
Full compliance with how Scala generates hash codes on case classes; …
ctongfei Aug 31, 2017
6906f1f
Cartesian[Hash]
ctongfei Aug 31, 2017
f6e2b0b
ContravariantCartesian[Hash]
ctongfei Aug 31, 2017
91115c0
ContravariantCartesian[Hash]; Hash.fromHashing
ctongfei Aug 31, 2017
d2bc3ac
style issues
ctongfei Sep 1, 2017
32b9bec
remove unused import
ctongfei Sep 1, 2017
3f22bc5
some test cases
ctongfei Sep 5, 2017
a4007e8
some test cases
ctongfei Sep 5, 2017
93b0c1c
+test for Contravariant[Hash]
ctongfei Sep 7, 2017
02402c5
+test for Contravariant[Hash]
ctongfei Sep 7, 2017
200aa23
Merge branch 'hash' into hash
ctongfei Sep 8, 2017
86b6947
Merge pull request #1 from LukaJCB/hash
ctongfei Sep 8, 2017
14e2c5c
Fix duplication error and style error
Sep 8, 2017
f7e6b7e
Merge pull request #2 from LukaJCB/hash
ctongfei Sep 8, 2017
a9b8313
fix merge error
ctongfei Sep 9, 2017
ef2e4f5
remove instance for Cartesian[Hash]: it does not satisfy associativity
ctongfei Sep 13, 2017
8856f8b
+identityHash, +`hash` postfix method
ctongfei Sep 13, 2017
34ae266
all tests passed
ctongfei Sep 17, 2017
a89b861
increase coverage
ctongfei Sep 17, 2017
9392100
accidentally removed plugin, restore it
ctongfei Sep 17, 2017
c24deb4
all tests passed
ctongfei Sep 17, 2017
cc034ea
increase coverage
ctongfei Sep 17, 2017
54b04eb
increase coverage, ## => hashCode, kernelBoiler
ctongfei Sep 18, 2017
136e203
suppress mimaReportBinaryIssues
ctongfei Oct 3, 2017
526757b
Merge branch 'master' into hash
Oct 4, 2017
49403c6
Remove cats.functor
Oct 4, 2017
601e18d
Remove cats.functor
Oct 4, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/scala/cats/instances/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ trait AllInstances
with TupleInstances
with UUIDInstances
with SymbolInstances
with HashInstances
18 changes: 18 additions & 0 deletions core/src/main/scala/cats/instances/hash.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package cats
package instances

import cats.functor._

/**
* @author Tongfei Chen
*/
trait HashInstances {
implicit val catsFunctorContravariantForHash: Contravariant[Hash] =
new Contravariant[Hash] {
/** Derive an `Hash` for `B` given an `Hash[A]` and a function `B => A`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nitpick, I obsess over details, but comment style isn't compatible with either ScalaDoc or JavaDoc. This title needs a new-line.

*
* Note: resulting instances are law-abiding only when the functions used are injective (represent a one-to-one mapping)
*/
def contramap[A, B](fa: Hash[A])(f: B => A): Hash[B] = Hash.by(f)(fa)
}
}
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,15 @@ 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]

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
Expand Down
38 changes: 38 additions & 0 deletions kernel-laws/src/main/scala/cats/kernel/laws/HashLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package cats.kernel
package laws

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

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

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

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

def hash(implicit A: Hash[A]): HashProperties = new HashProperties(
name = "hash",
parent = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this have EqLaws as parent?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OrderLaws's parent is also None. But EqLaws are listed below in the props. I copied that behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we should have EqLaws as a parent.

Copy link
Contributor Author

@ctongfei ctongfei Sep 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find EqLaws in the kernel-laws package.

Rules.serializable(Equ),
"compatibility-hash" -> forAll { (x: A, y: A) =>
!(A.eqv(x, y)) ?|| (A.hash(x) == A.hash(y))
}
)

class HashProperties(name: String, parent: Option[RuleSet], props: (String, Prop)*)
extends DefaultRuleSet(name, parent, props: _*)

}
19 changes: 19 additions & 0 deletions kernel-laws/src/test/scala/cats/kernel/laws/LawTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class LawTests extends FunSuite with Discipline {
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]

Expand Down Expand Up @@ -61,6 +62,24 @@ 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, Long].check(_.hash)
laws[HashLaws, BitSet].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[OrderLaws, List[HasEq[Int]]].check(_.eqv)
laws[OrderLaws, Option[HasEq[Int]]].check(_.eqv)
laws[OrderLaws, Vector[HasEq[Int]]].check(_.eqv)
Expand Down
52 changes: 52 additions & 0 deletions kernel/src/main/scala/cats/kernel/Hash.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider using Long and move away from the JVM legacy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fommil What are the justifications of using Long? JVM-default Int works good enough I think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Object#hashCode: Int in Java is basically used for distribution within Set or Map-like data structures and for that purpose Int will remain sufficient for some time, for one because the size of an array in Java can't exceed Int.MaxValue either and if you instantiate an Array<Int> of size Int.MaxValue that's around ~ 8 GB of RAM.

So I'm not seeing the size of an array evolving to a Long, for one because of backwards compatibility, but also it's probably counter productive to allow contiguous memory blocks bigger than that. For now at least.
I'm guessing that's the reason other languages use 32 bit integers (or sometimes less) for their hash codes as well. For example: https://hackage.haskell.org/package/hashable-1.2.6.1/docs/Data-Hashable.html

The questions are:

  1. besides doing distribution in data structures like HashMap / HashSet, are there any other use cases?
  2. doesn't Long have performance implications? it's an important question to answer, as HashMaps tend to be abused
  3. are there any languages using Long for their universal hashCode already and if so, I'd be interested in why?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

64 bit JVMs store both Int and Long as 64 bit numbers, so there is no advantage to use Int for space preserving reasons. However if you want to make use of existing hashCode algorithms that return Int then that would be a good reason to keep it that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that for now we should stay with what Object#hashCode is doing: returning an Int. Agree with @alexandru .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree about using Int. If you have a Long you can easily get an Int from int, but not the other way around. Some nice algorithms using hashes want more than 32 bits. For instance hyperloglog with only 32 bits would not be great.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to using Long here. If you want to just give me an Int upcast to Long that's on you, but it would be better to enable better algorithms.


// `Hash#on` deliberately not implement to avoid `Hash`/`Order` diamond inheritance problem.
// Please use `Hash.by` for the same functionality.
}

abstract class HashFunctions[H[T] <: Hash[T]] extends EqFunctions[H] {

def hash[@sp A](x: A)(implicit ev: H[A]): Int = ev hash x
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


}


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] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

new Hash[A] {
def hash(x: A) = ev.hash(f(x))
def eqv(x: A, y: A) = ev.eqv(f(x), f(y))
}

implicit def catsKernelHashingForHash[A](implicit ev: Hash[A]): Hashing[A] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested

new Hashing[A] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested

def hash(x: A): Int = ev.hash(x)
}

def fromUniversalHashCode[A]: Hash[A] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested

new Hash[A] {
def hash(x: A) = x.##
def eqv(x: A, y: A) = x == y
}

}
8 changes: 5 additions & 3 deletions kernel/src/main/scala/cats/kernel/instances/bigDecimal.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package instances
package object bigDecimal extends BigDecimalInstances // scalastyle:ignore package.object.name

trait BigDecimalInstances {
implicit val catsKernelStdOrderForBigDecimal: Order[BigDecimal] =
new BigDecimalOrder
implicit val catsKernelStdEqForBigDecimal: Order[BigDecimal] with Hash[BigDecimal] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not change this name for binary compatibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I will revert these.

new BigDecimalEq
implicit val catsKernelStdGroupForBigDecimal: CommutativeGroup[BigDecimal] =
new BigDecimalGroup
}
Expand All @@ -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 BigDecimalEq extends Order[BigDecimal] with Hash[BigDecimal] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not change the class name to preserve binary compatibility?


def hash(x: BigDecimal): Int = x.hashCode()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


def compare(x: BigDecimal, y: BigDecimal): Int = x compare y

Expand Down
7 changes: 4 additions & 3 deletions kernel/src/main/scala/cats/kernel/instances/bigInt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ package instances
package object bigInt extends BigIntInstances // scalastyle:ignore package.object.name

trait BigIntInstances {
implicit val catsKernelStdOrderForBigInt: Order[BigInt] =
new BigIntOrder
implicit val catsKernelStdEqForBigInt: Order[BigInt] with Hash[BigInt] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same binary compat issue.

new BigIntEq
implicit val catsKernelStdGroupForBigInt: CommutativeGroup[BigInt] =
new BigIntGroup
}
Expand All @@ -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 BigIntEq extends Order[BigInt] with Hash[BigInt] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same.


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
Expand Down
8 changes: 5 additions & 3 deletions kernel/src/main/scala/cats/kernel/instances/bitSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ import scala.collection.immutable.BitSet
package object bitSet extends BitSetInstances

trait BitSetInstances {
implicit val catsKernelStdPartialOrderForBitSet: PartialOrder[BitSet] =
new BitSetPartialOrder
implicit val catsKernelStdEqForBitSet: PartialOrder[BitSet] with Hash[BitSet] =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please don't change name

new BitSetPartialEq

implicit val catsKernelStdSemilatticeForBitSet: BoundedSemilattice[BitSet] =
new BitSetSemilattice
}

class BitSetPartialOrder extends PartialOrder[BitSet] {
class BitSetPartialEq 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
Expand Down
8 changes: 5 additions & 3 deletions kernel/src/main/scala/cats/kernel/instances/boolean.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ package instances
package object boolean extends BooleanInstances

trait BooleanInstances {
implicit val catsKernelStdOrderForBoolean: Order[Boolean] =
new BooleanOrder
implicit val catsKernelStdEqForBoolean: Order[Boolean] with Hash[Boolean] =
new BooleanEq
}

class BooleanOrder extends Order[Boolean] {
class BooleanEq extends Order[Boolean] with Hash[Boolean] {

def hash(x: Boolean): Int = x.##
def compare(x: Boolean, y: Boolean): Int =
if (x == y) 0 else if (x) 1 else -1

Expand Down
6 changes: 4 additions & 2 deletions kernel/src/main/scala/cats/kernel/instances/byte.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package instances
package object byte extends ByteInstances

trait ByteInstances {
implicit val catsKernelStdOrderForByte: Order[Byte] = new ByteOrder
implicit val catsKernelStdEqForByte: Order[Byte] with Hash[Byte] = new ByteEq
implicit val catsKernelStdGroupForByte: CommutativeGroup[Byte] = new ByteGroup
}

Expand All @@ -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 ByteEq extends Order[Byte] with Hash[Byte] {

def hash(x: Byte): Int = x.##

def compare(x: Byte, y: Byte): Int =
if (x < y) -1 else if (x > y) 1 else 0
Expand Down
5 changes: 3 additions & 2 deletions kernel/src/main/scala/cats/kernel/instances/char.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ package instances
package object char extends CharInstances

trait CharInstances {
implicit val catsKernelStdOrderForChar = new CharOrder
implicit val catsKernelStdEqForChar: Order[Char] with Hash[Char] = new CharEq
}

class CharOrder extends Order[Char] {
class CharEq extends Order[Char] with Hash[Char] {
def hash(x: Char): Int = x.##
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
Expand Down
7 changes: 3 additions & 4 deletions kernel/src/main/scala/cats/kernel/instances/double.scala
Original file line number Diff line number Diff line change
@@ -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 catsKernelStdEqForDouble: Order[Double] with Hash[Double] = new DoubleEq
implicit val catsKernelStdGroupForDouble: CommutativeGroup[Double] = new DoubleGroup
}

Expand All @@ -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 DoubleEq extends Order[Double] with Hash[Double] {

def hash(x: Double): Int = x.##
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested

def compare(x: Double, y: Double): Int =
java.lang.Double.compare(x, y)

Expand Down
40 changes: 27 additions & 13 deletions kernel/src/main/scala/cats/kernel/instances/either.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,35 @@ trait EitherInstances0 extends EitherInstances1 {
}
}
}
}

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 =
implicit def catsStdHashForEither[A, B](implicit A: Hash[A], B: Hash[B]): Hash[Either[A, B]] =
new EitherEq[A, B](A, B) with Hash[Either[A, B]] {
def hash(x: Either[A, B]): Int = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

untested

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)
}
case Left(xx) => Left(A.hash(xx)).##
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not allocate just to get the hashcode.

Something like Left(xx) => A.hash(xx) * 65521 Right(xx) => B.hash(xx) seems fine:
mixing the left with a prime about half the size of the int space (taken from the largest 16 bit here):
http://www.risc.jku.at/people/hemmecke/AldorCombinat/combinatse20.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to have the default Hash instance for Either[A, B] perform the exact same way as the universal hash (##) given the Hash instance for A and B are universal hashes.
So maybe I'll copy the function implementation for case classes in Scala?
Basically I want EitherHash[A, B](DefaultHashInstance[A], DefaultHashInstance[B])(Left(a)) == Left(a).##.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe I'll copy the function implementation for case classes in Scala?

👍

case Right(xx) => Right(B.hash(xx)).##
}
}
}
}

trait EitherInstances1 {

// isolated class for inheritance
private[cats] class EitherEq[A, B](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)
}
}
}

implicit def catsStdEqForEither[A, B](implicit A: Eq[A], B: Eq[B]): Eq[Either[A, B]] = new EitherEq(A, B)

}
6 changes: 4 additions & 2 deletions kernel/src/main/scala/cats/kernel/instances/float.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package instances
package object float extends FloatInstances

trait FloatInstances {
implicit val catsKernelStdOrderForFloat: Order[Float] = new FloatOrder
implicit val catsKernelStdEqForFloat: Order[Float] with Hash[Float] = new FloatEq
implicit val catsKernelStdGroupForFloat: CommutativeGroup[Float] = new FloatGroup
}

Expand All @@ -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 FloatEq extends Order[Float] with Hash[Float] {

def hash(x: Float): Int = x.##

def compare(x: Float, y: Float): Int =
java.lang.Float.compare(x, y)
Expand Down
6 changes: 6 additions & 0 deletions kernel/src/main/scala/cats/kernel/instances/function.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Loading