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

Scala 2.13.0 and Hash derivation rework #157

Merged
merged 1 commit into from
Jun 13, 2019
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
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ language: scala
scala:
- 2.12.8
- 2.11.12
- 2.13.0-M5
- 2.13.0

jdk:
- oraclejdk8
Expand Down
15 changes: 6 additions & 9 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import sbtcrossproject.{CrossType, crossProject}

lazy val buildSettings = Seq(
organization := "org.typelevel",
scalaVersion := "2.12.8",
crossScalaVersions := Seq("2.11.12", scalaVersion.value, "2.13.0-M5")
scalaVersion := "2.13.0",
crossScalaVersions := Seq("2.11.12", "2.12.8", scalaVersion.value)
)

val catsVersion = "1.6.0"
val catsVersion = "2.0.0-M4"

lazy val commonSettings = Seq(
scalacOptions := Seq(
Expand All @@ -22,11 +22,8 @@ lazy val commonSettings = Seq(
),
scalacOptions ++= (
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, v)) if v <= 12 => Seq(
"-Ypartial-unification"
)
case _ => Seq(
)
case Some((2, v)) if v <= 12 => Seq("-Ypartial-unification")
case _ => Seq.empty
}
),
resolvers ++= Seq(
Expand All @@ -39,7 +36,7 @@ lazy val commonSettings = Seq(
"org.typelevel" %% "alleycats-core" % catsVersion,
"com.chuusai" %% "shapeless" % "2.3.3",
"org.typelevel" %% "cats-testkit" % catsVersion % "test",
compilerPlugin("org.spire-math" %% "kind-projector" % "0.9.9")
compilerPlugin("org.typelevel" %% "kind-projector" % "0.10.3")
),
scmInfo :=
Some(ScmInfo(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cats.derived.util

import scala.util.hashing.MurmurHash3

private[derived] object VersionSpecific {
def productSeed(x: Product): Int = MurmurHash3.productSeed
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cats.derived.util

import scala.util.hashing.MurmurHash3

private[derived] object VersionSpecific {
def productSeed(x: Product): Int = MurmurHash3.productSeed
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package cats.derived.util

import scala.util.hashing.MurmurHash3

private[derived] object VersionSpecific {
def productSeed(x: Product): Int = MurmurHash3.mix(MurmurHash3.productSeed, x.productPrefix.hashCode)
}
147 changes: 79 additions & 68 deletions core/src/main/scala/cats/derived/hash.scala
Original file line number Diff line number Diff line change
@@ -1,91 +1,102 @@
package cats.derived
package cats
package derived

import cats.Hash
import shapeless._, labelled._
import scala.annotation.{implicitNotFound, tailrec}
import scala.util.hashing.MurmurHash3

@implicitNotFound("Could not derive an instance of Hash[${A}]")
trait MkHash[A] extends Hash[A]



object MkHash extends MkHash0 {
def apply[A](implicit hash: MkHash[A]): MkHash[A] = hash


object MkHash extends MkHashDerivation {
def apply[A](implicit ev: MkHash[A]): MkHash[A] = ev
}

private[derived] trait HashBuilder[A] {
def hashes(a: A): List[Int]
def hashes(x: A): List[Int]
def eqv(x: A, y: A): Boolean
}

object HashBuilder {
implicit val emptyProductDerivedHash: HashBuilder[HNil] = new HashBuilder[HNil] {
override def hashes(x: HNil): List[Int] = Nil
override def eqv(x: HNil, y: HNil): Boolean = true
}


implicit def productDerivedHash[H, T <: HList](
implicit
hashH: Hash[H] OrElse MkHash[H],
hashT: HashBuilder[T]): HashBuilder[H :: T] = new HashBuilder[H :: T] {
def hashes(fields: H :: T): List[Int] = {
val h = hashH.unify.hash(fields.head)
val t = hashT.hashes(fields.tail)
h :: t
def hash(x: A, seed: Int): Int = {
@tailrec def loop(hashes: List[Int], hash: Int, length: Int): Int = hashes match {
case head :: tail => loop(tail, MurmurHash3.mix(hash, head), length + 1)
case Nil => MurmurHash3.finalizeHash(hash, length)
}

def eqv(x: H :: T, y: H :: T): Boolean =
hashH.unify.eqv(x.head, y.head) && hashT.eqv(x.tail, y.tail)
loop(hashes(x), seed, 0)
}
}

trait MkHash0 extends MkHash1 {
implicit def deriveHashCaseObject[A](
implicit repr: Generic.Aux[A, HNil]): MkHash[A] = new MkHash[A] {
def hash(x: A): Int = x.hashCode
private[derived] object HashBuilder {
import shapeless._

def eqv(x: A, y: A): Boolean = true
}
implicit val hashBuilderHNil: HashBuilder[HNil] =
instance(_ => Nil, (_, _) => true)

}
implicit def hashBuilderHCons[H, T <: HList](
implicit H: Hash[H] OrElse MkHash[H], T: HashBuilder[T]
): HashBuilder[H :: T] = instance(
{ case h :: t => H.unify.hash(h) :: T.hashes(t) },
{ case (hx :: tx, hy :: ty) => H.unify.eqv(hx, hy) && T.eqv(tx, ty) }
)

trait MkHash1 {
implicit def fromBuilder[A](implicit builder: HashBuilder[A]): MkHash[A] = new MkHash[A] {
override def hash(x: A): Int = {
val hashes = builder.hashes(x)
runtime.Statics.finalizeHash(hashes.foldLeft(-889275714)(runtime.Statics.mix), hashes.length)
private def instance[A](f: A => List[Int], g: (A, A) => Boolean): HashBuilder[A] =
new HashBuilder[A] {
def hashes(x: A) = f(x)
def eqv(x: A, y: A) = g(x, y)
}
}

override def eqv(x: A, y: A): Boolean = builder.eqv(x, y)
}

implicit def emptyCoproductDerivedHash: MkHash[CNil] = null
// used when Hash[V] (a member of the coproduct) has to be derived.
implicit def coproductDerivedHash[L, R <: Coproduct](
implicit
hashV: Hash[L] OrElse MkHash[L],
hashT: MkHash[R]): MkHash[L :+: R] = new MkHash[L :+: R] {
def hash(value: L :+: R): Int = value match {
case Inl(l) => hashV.unify.hash(l)
case Inr(r) => hashT.hash(r)
}

def eqv(x: L :+: R, y: L :+: R): Boolean =
(x, y) match {
case (Inl(xl), Inl(yl)) => hashV.unify.eqv(xl, yl)
case (Inr(xr), Inr(yr)) => hashT.eqv(xr, yr)
case _ => false
}

}
private[derived] abstract class MkHashDerivation extends MkHashGenericProduct {
import shapeless._

implicit val mkHashCNil: MkHash[CNil] =
instance(_ => 0, (_, _) => true)

implicit def mkHashCCons[L, R <: Coproduct](
implicit L: Hash[L] OrElse MkHash[L], R: MkHash[R]
): MkHash[L :+: R] = instance({
case Inl(l) => L.unify.hash(l)
case Inr(r) => R.hash(r)
}, {
case (Inl(lx), Inl(ly)) => L.unify.eqv(lx, ly)
case (Inr(rx), Inr(ry)) => R.eqv(rx, ry)
case _ => false
})

implicit def mkHashCaseObject[A](implicit A: Generic.Aux[A, HNil]): MkHash[A] =
instance(_.hashCode, (_, _) => true)
}

implicit def genericDerivedHash[A, R](
implicit gen: Generic.Aux[A, R],
s: Lazy[MkHash[R]]): MkHash[A] = new MkHash[A] {
def hash(a: A): Int = s.value.hash(gen.to(a))
private[derived] abstract class MkHashGenericProduct extends MkHashGeneric {
import shapeless._

def eqv(x: A, y: A): Boolean = s.value.eqv(gen.to(x), gen.to(y))
}
implicit def mkHashGenericProduct[A, R <: HList](
implicit A: Generic.Aux[A, R], R: Lazy[HashBuilder[R]], ev: A <:< Product
): MkHash[A] = instance(
x => R.value.hash(A.to(x), util.VersionSpecific.productSeed(x)),
(x, y) => R.value.eqv(A.to(x), A.to(y))
)
}

private[derived] abstract class MkHashGeneric {
import shapeless._

implicit def mkHashGenericHList[A, R <: HList](
implicit A: Generic.Aux[A, R], R: Lazy[HashBuilder[R]]
): MkHash[A] = instance(
x => R.value.hash(A.to(x), MurmurHash3.productSeed),
(x, y) => R.value.eqv(A.to(x), A.to(y))
)

implicit def mkHashGenericCoproduct[A, R <: Coproduct](
implicit A: Generic.Aux[A, R], R: Lazy[MkHash[R]]
): MkHash[A] = instance(
x => R.value.hash(A.to(x)),
(x, y) => R.value.eqv(A.to(x), A.to(y))
)

protected def instance[A](f: A => Int, g: (A, A) => Boolean): MkHash[A] =
new MkHash[A] {
def hash(x: A) = f(x)
def eqv(x: A, y: A) = g(x, y)
}
}
10 changes: 5 additions & 5 deletions core/src/main/scala/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ object auto {

object hash {
implicit def kittensMkHash[A](
implicit refute: Refute[Hash[A]], hash: MkHash[A]
): Hash[A] = hash
implicit refute: Refute[Hash[A]], ev: Lazy[MkHash[A]]
): Hash[A] = ev.value
}

object functor {
Expand Down Expand Up @@ -174,8 +174,8 @@ object cached {

object hash {
implicit def kittensMkHash[A](
implicit refute: Refute[Hash[A]], ord: Cached[MkHash[A]])
: Hash[A] = ord.value
implicit refute: Refute[Hash[A]], cached: Cached[MkHash[A]]
): Hash[A] = cached.value
}

object functor {
Expand Down Expand Up @@ -294,7 +294,7 @@ object semi {

def order[A](implicit ev: Lazy[MkOrder[A]]): Order[A] = ev.value

def hash[A](implicit ev: MkHash[A]): Hash[A] = ev
def hash[A](implicit ev: Lazy[MkHash[A]]): Hash[A] = ev.value

def functor[F[_]](implicit F: Lazy[MkFunctor[F]]): Functor[F] = F.value

Expand Down
4 changes: 2 additions & 2 deletions core/src/test/scala/cats/derived/KittensSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package cats.derived

import cats.syntax.AllSyntax
import org.scalatest.FunSuite
import org.scalatest.funsuite.AnyFunSuite
import org.typelevel.discipline.scalatest.Discipline

import scala.util.control.NonFatal
Expand All @@ -28,7 +28,7 @@ import scala.util.control.NonFatal
* CatsSuite in the Cat project, this trait does not mix in any
* instances.
*/
trait KittensSuite extends FunSuite with Discipline with AllSyntax with SerializableTest
trait KittensSuite extends AnyFunSuite with Discipline with AllSyntax with SerializableTest


trait SerializableTest {
Expand Down
7 changes: 3 additions & 4 deletions core/src/test/scala/cats/derived/consk.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@
package cats.derived

import alleycats.ConsK
import org.scalacheck.Arbitrary
import org.scalatest.prop.GeneratorDrivenPropertyChecks
import org.scalatest.prop.{Generator, GeneratorDrivenPropertyChecks}

class ConsKSuite extends KittensSuite with GeneratorDrivenPropertyChecks {
import TestDefns._

def checkConsK[F[_], A: Arbitrary](nil: F[A])(fromSeq: Seq[A] => F[A])(implicit F: ConsK[F]): Unit =
forAll((xs: Seq[A]) => assert(xs.foldRight(nil)(F.cons) == fromSeq(xs)))
def checkConsK[F[_], A: Generator](nil: F[A])(fromSeq: Seq[A] => F[A])(implicit F: ConsK[F]): Unit =
forAll((xs: List[A]) => assert(xs.foldRight(nil)(F.cons) == fromSeq(xs)))

def testConsK(context: String)(implicit iList: ConsK[IList], snoc: ConsK[Snoc]): Unit = {
test(s"$context.ConsK[IList]")(checkConsK[IList, Int](INil())(IList.fromSeq))
Expand Down
Loading