Skip to content

Commit 8e98f0b

Browse files
authored
Merge pull request #3944 from SimY4/topic/identity-hash
add identity hash to alleycats
2 parents 223f759 + f54101c commit 8e98f0b

File tree

7 files changed

+104
-43
lines changed

7 files changed

+104
-43
lines changed

alleycats-core/src/main/scala/alleycats/ReferentialEq.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@ import cats.Eq
66
* An `Eq[A]` that delegates to referential equality (`eq`).
77
* Note that it is not referentially transparent!
88
*/
9+
trait ReferentialEq[A <: AnyRef] extends Eq[A] {
10+
def eqv(x: A, y: A): Boolean = x eq y
11+
}
12+
913
object ReferentialEq {
10-
def apply[A <: AnyRef]: Eq[A] = new Eq[A] {
11-
def eqv(x: A, y: A) = x eq y
12-
}
14+
private[this] val referentialEq: Eq[AnyRef] = new ReferentialEq[AnyRef] {}
15+
16+
def apply[A <: AnyRef]: Eq[A] = referentialEq.asInstanceOf[Eq[A]]
1317
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package alleycats
2+
3+
import cats.Hash
4+
5+
/**
6+
* A `Hash[A]` that delegates to identity hashcode (`System.identityHashCode`).
7+
* It implements `Eq[A]` via referential equality (`eq`) that it is not referentially transparent!
8+
*/
9+
trait SystemIdentityHash[A <: AnyRef] extends ReferentialEq[A] with Hash[A] {
10+
override def hash(a: A): Int = java.lang.System.identityHashCode(a)
11+
}
12+
13+
object SystemIdentityHash {
14+
private[this] val identityHash: Hash[AnyRef] = new SystemIdentityHash[AnyRef] {}
15+
16+
def apply[A <: AnyRef]: Hash[A] = identityHash.asInstanceOf[Hash[A]]
17+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package alleycats.laws.discipline
2+
3+
import cats.kernel.Eq
4+
import cats.kernel.laws.EqLaws
5+
import cats.kernel.laws.discipline._
6+
import org.scalacheck.Arbitrary
7+
import org.scalacheck.Prop.forAll
8+
import org.typelevel.discipline.Laws
9+
10+
trait ReferentialEqTests[A] extends Laws {
11+
def laws: EqLaws[A]
12+
13+
def eqv(implicit arbA: Arbitrary[A]): RuleSet = {
14+
implicit val eqA: Eq[A] = laws.E
15+
new DefaultRuleSet(
16+
"referentialEq",
17+
None,
18+
"reflexivity eq" -> forAll(laws.reflexivityEq _),
19+
"symmetry eq" -> forAll(laws.symmetryEq _),
20+
"transitivity eq" -> forAll(laws.transitivityEq _)
21+
)
22+
}
23+
}
24+
25+
object ReferentialEqTests {
26+
def apply[A: Eq]: ReferentialEqTests[A] = new ReferentialEqTests[A] {
27+
override def laws: EqLaws[A] = EqLaws[A]
28+
}
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package alleycats.laws.discipline
2+
3+
import cats.kernel.{Eq, Hash}
4+
import cats.kernel.laws.HashLaws
5+
import cats.kernel.laws.discipline._
6+
import org.scalacheck.Arbitrary
7+
import org.scalacheck.Prop.forAll
8+
9+
import scala.util.hashing.Hashing
10+
11+
trait SystemIdentityHashTests[A] extends ReferentialEqTests[A] {
12+
def laws: HashLaws[A]
13+
14+
def hash(implicit arbA: Arbitrary[A], eqA: Eq[A], hashA: Hashing[A]): RuleSet =
15+
new DefaultRuleSet(
16+
"systemIdentityHash",
17+
Some(eqv),
18+
"hash compatibility" -> forAll(laws.hashCompatibility _),
19+
"same as universal hash" -> forAll(laws.sameAsUniversalHash _),
20+
"same as scala hashing" -> forAll((x: A, y: A) => laws.sameAsScalaHashing(x, y, hashA))
21+
)
22+
}
23+
24+
object SystemIdentityHashTests {
25+
def apply[A: Hash]: SystemIdentityHashTests[A] = new SystemIdentityHashTests[A] {
26+
override def laws: HashLaws[A] = HashLaws[A]
27+
}
28+
}

alleycats-tests/shared/src/test/scala/alleycats/tests/AlleycatsSuite.scala

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ import alleycats.std.MapInstances
44
import cats._
55
import cats.instances.all._
66
import org.scalacheck.{Arbitrary, Gen}
7-
import org.scalacheck.Arbitrary.arbitrary
8-
import scala.util.{Failure, Success, Try}
97
import org.scalacheck.Test.Parameters
108

119
/**
@@ -20,7 +18,14 @@ trait AlleycatsSuite extends munit.DisciplineSuite with TestSettings with TestIn
2018
}
2119

2220
sealed trait TestInstances {
23-
// To be replaced by https://github.com/rickynils/scalacheck/pull/170
24-
implicit def arbitraryTry[A: Arbitrary]: Arbitrary[Try[A]] =
25-
Arbitrary(Gen.oneOf(arbitrary[A].map(Success(_)), arbitrary[Throwable].map(Failure(_))))
21+
implicit val arbObject: Arbitrary[Object] =
22+
// with some probability we select from a small set of objects
23+
// otherwise make a totally new one
24+
// courtesy of @johnynek
25+
Arbitrary(
26+
Gen.oneOf(
27+
Gen.oneOf(List.fill(5)(new Object)),
28+
Arbitrary.arbUnit.arbitrary.map(_ => new Object)
29+
)
30+
)
2631
}
Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,11 @@
11
package alleycats.tests
22

33
import alleycats.ReferentialEq
4+
import alleycats.laws.discipline.ReferentialEqTests
45
import cats.kernel.Eq
5-
import cats.kernel.laws.discipline._
6-
import cats.kernel.laws.EqLaws
7-
import org.scalacheck.Arbitrary
8-
import org.scalacheck.Gen
9-
import org.scalacheck.Prop.forAll
10-
import org.typelevel.discipline.Laws
116

127
class ReferentialEqSuite extends AlleycatsSuite {
13-
14-
class ReferentialEqTests[A](eq: Eq[A]) extends Laws {
15-
def laws = EqLaws(eq)
16-
17-
def eqv(implicit arbA: Arbitrary[A]): RuleSet = {
18-
implicit val eqA: Eq[A] = laws.E
19-
new DefaultRuleSet(
20-
"referentialEq",
21-
None,
22-
"reflexivity eq" -> forAll(laws.reflexivityEq _),
23-
"symmetry eq" -> forAll(laws.symmetryEq _),
24-
"transitivity eq" -> forAll(laws.transitivityEq _)
25-
)
26-
}
27-
}
28-
29-
implicit val arbObject: Arbitrary[Object] =
30-
// with some probability we select from a small set of objects
31-
// otherwise make a totally new one
32-
// courtesy of @johnynek
33-
Arbitrary(
34-
Gen.oneOf(
35-
Gen.oneOf(List.fill(5)(new Object)),
36-
Arbitrary.arbUnit.arbitrary.map(_ => new Object)
37-
)
38-
)
39-
408
implicit val eqObject: Eq[Object] = ReferentialEq[Object]
419

42-
checkAll("ReferentialEq[Object]", new ReferentialEqTests(ReferentialEq[Object]).eqv)
43-
10+
checkAll("ReferentialEq[Object]", ReferentialEqTests[Object].eqv)
4411
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package alleycats.tests
2+
3+
import alleycats.SystemIdentityHash
4+
import alleycats.laws.discipline.SystemIdentityHashTests
5+
import cats.kernel.Hash
6+
7+
class SystemIdentityHashSuite extends AlleycatsSuite {
8+
implicit val hashObject: Hash[Object] = SystemIdentityHash[Object]
9+
10+
checkAll("SystemIdentityHash[Object]", SystemIdentityHashTests[Object].hash)
11+
}

0 commit comments

Comments
 (0)