diff --git a/alleycats-core/src/main/scala/alleycats/ReferentialEq.scala b/alleycats-core/src/main/scala/alleycats/ReferentialEq.scala index 7f1013cbab..e922612207 100644 --- a/alleycats-core/src/main/scala/alleycats/ReferentialEq.scala +++ b/alleycats-core/src/main/scala/alleycats/ReferentialEq.scala @@ -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]] } diff --git a/alleycats-core/src/main/scala/alleycats/SystemIdentityHash.scala b/alleycats-core/src/main/scala/alleycats/SystemIdentityHash.scala new file mode 100644 index 0000000000..170e07f8c8 --- /dev/null +++ b/alleycats-core/src/main/scala/alleycats/SystemIdentityHash.scala @@ -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) +} + +object SystemIdentityHash { + private[this] val identityHash: Hash[AnyRef] = new SystemIdentityHash[AnyRef] {} + + def apply[A <: AnyRef]: Hash[A] = identityHash.asInstanceOf[Hash[A]] +} diff --git a/alleycats-laws/src/main/scala/alleycats/laws/discipline/ReferentialEqTests.scala b/alleycats-laws/src/main/scala/alleycats/laws/discipline/ReferentialEqTests.scala new file mode 100644 index 0000000000..5bc5c169fc --- /dev/null +++ b/alleycats-laws/src/main/scala/alleycats/laws/discipline/ReferentialEqTests.scala @@ -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] + } +} diff --git a/alleycats-laws/src/main/scala/alleycats/laws/discipline/SystemIdentityHashTests.scala b/alleycats-laws/src/main/scala/alleycats/laws/discipline/SystemIdentityHashTests.scala new file mode 100644 index 0000000000..1e88a76010 --- /dev/null +++ b/alleycats-laws/src/main/scala/alleycats/laws/discipline/SystemIdentityHashTests.scala @@ -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] + } +} diff --git a/alleycats-tests/shared/src/test/scala/alleycats/tests/AlleycatsSuite.scala b/alleycats-tests/shared/src/test/scala/alleycats/tests/AlleycatsSuite.scala index 621f55de25..72df380181 100644 --- a/alleycats-tests/shared/src/test/scala/alleycats/tests/AlleycatsSuite.scala +++ b/alleycats-tests/shared/src/test/scala/alleycats/tests/AlleycatsSuite.scala @@ -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 /** @@ -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) + ) + ) } diff --git a/alleycats-tests/shared/src/test/scala/alleycats/tests/ReferentialEqSuite.scala b/alleycats-tests/shared/src/test/scala/alleycats/tests/ReferentialEqSuite.scala index 0e2810f2d5..c4490301a9 100644 --- a/alleycats-tests/shared/src/test/scala/alleycats/tests/ReferentialEqSuite.scala +++ b/alleycats-tests/shared/src/test/scala/alleycats/tests/ReferentialEqSuite.scala @@ -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 { - 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) } diff --git a/alleycats-tests/shared/src/test/scala/alleycats/tests/SystemIdentityHashSuite.scala b/alleycats-tests/shared/src/test/scala/alleycats/tests/SystemIdentityHashSuite.scala new file mode 100644 index 0000000000..580b7bef38 --- /dev/null +++ b/alleycats-tests/shared/src/test/scala/alleycats/tests/SystemIdentityHashSuite.scala @@ -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) +}