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

add identity hash to alleycats #3944

Merged
merged 3 commits into from
Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 7 additions & 3 deletions alleycats-core/src/main/scala/alleycats/ReferentialEq.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ import cats.Eq
* An `Eq[A]` that delegates to referential equality (`eq`).
* Note that it is not referentially transparent!
*/
trait ReferentialEq[A <: AnyRef] extends Eq[A] {
def eqv(x: A, y: A): Boolean = x eq y
}

object ReferentialEq {
def apply[A <: AnyRef]: Eq[A] = new Eq[A] {
def eqv(x: A, y: A) = x eq y
}
private[this] val referentialEq: Eq[AnyRef] = new ReferentialEq[AnyRef] {}

def apply[A <: AnyRef]: Eq[A] = referentialEq.asInstanceOf[Eq[A]]
}
17 changes: 17 additions & 0 deletions alleycats-core/src/main/scala/alleycats/SystemIdentityHash.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package alleycats

import cats.Hash

/**
* A `Hash[A]` that delegates to identity hashcode (`System.identityHashCode`).
* It implements `Eq[A]` via referential equality (`eq`) that it is not referentially transparent!
*/
trait SystemIdentityHash[A <: AnyRef] extends ReferentialEq[A] with Hash[A] {
override def hash(a: A): Int = java.lang.System.identityHashCode(a)
}
SimY4 marked this conversation as resolved.
Show resolved Hide resolved

object SystemIdentityHash {
private[this] val identityHash: Hash[AnyRef] = new SystemIdentityHash[AnyRef] {}

def apply[A <: AnyRef]: Hash[A] = identityHash.asInstanceOf[Hash[A]]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package alleycats.laws.discipline

import cats.kernel.Eq
import cats.kernel.laws.EqLaws
import cats.kernel.laws.discipline._
import org.scalacheck.Arbitrary
import org.scalacheck.Prop.forAll
import org.typelevel.discipline.Laws

trait ReferentialEqTests[A] extends Laws {
def laws: EqLaws[A]

def eqv(implicit arbA: Arbitrary[A]): RuleSet = {
implicit val eqA: Eq[A] = laws.E
new DefaultRuleSet(
"referentialEq",
None,
"reflexivity eq" -> forAll(laws.reflexivityEq _),
"symmetry eq" -> forAll(laws.symmetryEq _),
"transitivity eq" -> forAll(laws.transitivityEq _)
)
}
}

object ReferentialEqTests {
def apply[A: Eq]: ReferentialEqTests[A] = new ReferentialEqTests[A] {
override def laws: EqLaws[A] = EqLaws[A]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package alleycats.laws.discipline

import cats.kernel.{Eq, Hash}
import cats.kernel.laws.HashLaws
import cats.kernel.laws.discipline._
import org.scalacheck.Arbitrary
import org.scalacheck.Prop.forAll

import scala.util.hashing.Hashing

trait SystemIdentityHashTests[A] extends ReferentialEqTests[A] {
def laws: HashLaws[A]

def hash(implicit arbA: Arbitrary[A], eqA: Eq[A], hashA: Hashing[A]): RuleSet =
new DefaultRuleSet(
"systemIdentityHash",
Some(eqv),
"hash compatibility" -> forAll(laws.hashCompatibility _),
"same as universal hash" -> forAll(laws.sameAsUniversalHash _),
"same as scala hashing" -> forAll((x: A, y: A) => laws.sameAsScalaHashing(x, y, hashA))
)
}

object SystemIdentityHashTests {
def apply[A: Hash]: SystemIdentityHashTests[A] = new SystemIdentityHashTests[A] {
override def laws: HashLaws[A] = HashLaws[A]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import alleycats.std.MapInstances
import cats._
import cats.instances.all._
import org.scalacheck.{Arbitrary, Gen}
import org.scalacheck.Arbitrary.arbitrary
import scala.util.{Failure, Success, Try}
import org.scalacheck.Test.Parameters

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

sealed trait TestInstances {
// To be replaced by https://github.com/rickynils/scalacheck/pull/170
implicit def arbitraryTry[A: Arbitrary]: Arbitrary[Try[A]] =
Arbitrary(Gen.oneOf(arbitrary[A].map(Success(_)), arbitrary[Throwable].map(Failure(_))))
implicit val arbObject: Arbitrary[Object] =
// with some probability we select from a small set of objects
// otherwise make a totally new one
// courtesy of @johnynek
Arbitrary(
Gen.oneOf(
Gen.oneOf(List.fill(5)(new Object)),
Arbitrary.arbUnit.arbitrary.map(_ => new Object)
)
)
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,11 @@
package alleycats.tests

import alleycats.ReferentialEq
import alleycats.laws.discipline.ReferentialEqTests
import cats.kernel.Eq
import cats.kernel.laws.discipline._
import cats.kernel.laws.EqLaws
import org.scalacheck.Arbitrary
import org.scalacheck.Gen
import org.scalacheck.Prop.forAll
import org.typelevel.discipline.Laws

class ReferentialEqSuite extends AlleycatsSuite {

class ReferentialEqTests[A](eq: Eq[A]) extends Laws {
SimY4 marked this conversation as resolved.
Show resolved Hide resolved
def laws = EqLaws(eq)

def eqv(implicit arbA: Arbitrary[A]): RuleSet = {
implicit val eqA: Eq[A] = laws.E
new DefaultRuleSet(
"referentialEq",
None,
"reflexivity eq" -> forAll(laws.reflexivityEq _),
"symmetry eq" -> forAll(laws.symmetryEq _),
"transitivity eq" -> forAll(laws.transitivityEq _)
)
}
}

implicit val arbObject: Arbitrary[Object] =
// with some probability we select from a small set of objects
// otherwise make a totally new one
// courtesy of @johnynek
Arbitrary(
Gen.oneOf(
Gen.oneOf(List.fill(5)(new Object)),
Arbitrary.arbUnit.arbitrary.map(_ => new Object)
)
)

implicit val eqObject: Eq[Object] = ReferentialEq[Object]

checkAll("ReferentialEq[Object]", new ReferentialEqTests(ReferentialEq[Object]).eqv)

checkAll("ReferentialEq[Object]", ReferentialEqTests[Object].eqv)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package alleycats.tests

import alleycats.SystemIdentityHash
import alleycats.laws.discipline.SystemIdentityHashTests
import cats.kernel.Hash

class SystemIdentityHashSuite extends AlleycatsSuite {
implicit val hashObject: Hash[Object] = SystemIdentityHash[Object]

checkAll("SystemIdentityHash[Object]", SystemIdentityHashTests[Object].hash)
}