-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Hash typeclass #1712
Changes from 9 commits
9587384
a1ef839
b7fb209
04c1f96
e6e6159
66468e0
92f808c
69d4259
932903d
c8b3d5e
650e4f5
bb043a6
d087613
a4f96b5
7edb0c5
8abefa0
703070b
b8ee98f
5ebd13d
8a950f8
0cd2b48
c3f6378
48034eb
987ab6d
a432ace
9f9fd1a
ab539d1
951561f
927a0c7
0101cf2
1005e3e
cf3218a
6ea5667
d4baf94
222fc29
522b9ad
fcb355c
9397e2a
2cc9854
54ce4f8
37af90e
e114dc3
52eceac
737e2ec
4ddd79f
a7d3e3a
1225a06
517bd3c
ece3413
177edf5
e170d22
916415c
0dd4a12
49fa9ae
bf96364
f645496
a59f9c7
79b7f1a
4cedb1f
d397983
f772a7a
6906f1f
f6e2b0b
91115c0
d2bc3ac
32b9bec
3f22bc5
a4007e8
93b0c1c
02402c5
200aa23
86b6947
14e2c5c
f7e6b7e
a9b8313
ef2e4f5
8856f8b
34ae266
a89b861
9392100
c24deb4
cc034ea
54b04eb
136e203
526757b
49403c6
601e18d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,3 +25,4 @@ trait AllInstances | |
with TupleInstances | ||
with UUIDInstances | ||
with SymbolInstances | ||
with HashInstances |
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`. | ||
* | ||
* 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) | ||
} | ||
} |
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this have There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like we should have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't find |
||
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: _*) | ||
|
||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. consider using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @fommil What are the justifications of using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The So I'm not seeing the size of an array evolving to a The questions are:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 64 bit JVMs store both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that for now we should stay with what There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I disagree about using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 to using |
||
|
||
// `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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. untested. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. untested There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. untested |
||
new Hashing[A] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we not change this name for binary compatibility? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
@@ -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] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. untested There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
|
||
def compare(x: BigDecimal, y: BigDecimal): Int = x compare y | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
@@ -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] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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] = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
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 | ||
} | ||
|
||
|
@@ -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.## | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)).## | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we not allocate just to get the hashcode. Something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I want to have the default Hash instance for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
👍 |
||
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) | ||
|
||
} |
There was a problem hiding this comment.
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.