Skip to content

Commit

Permalink
optimize combineAllOption, fix #23
Browse files Browse the repository at this point in the history
  • Loading branch information
nevillelyh committed Dec 30, 2019
1 parent 546bfdf commit 0f15d0c
Show file tree
Hide file tree
Showing 8 changed files with 159 additions and 54 deletions.
22 changes: 22 additions & 0 deletions cats/src/main/scala-2.11/magnolify/cats/semiauto/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2019 Spotify AB.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package magnolify.cats

package object semiauto {
// Expose cats.kernel.compat.scalaVersionSpecific
type IterableOnce[+A] = TraversableOnce[A]
}
22 changes: 22 additions & 0 deletions cats/src/main/scala-2.12/magnolify/cats/semiauto/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2019 Spotify AB.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package magnolify.cats

package object semiauto {
// Expose cats.kernel.compat.scalaVersionSpecific
type IterableOnce[+A] = TraversableOnce[A]
}
32 changes: 21 additions & 11 deletions cats/src/main/scala/magnolify/cats/semiauto/EqDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,27 @@ import scala.language.experimental.macros
object EqDerivation {
type Typeclass[T] = Eq[T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = Eq.instance { (x, y) =>
caseClass.parameters.forall { p =>
p.typeclass.eqv(p.dereference(x), p.dereference(y))
}
}

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = Eq.instance { (x, y) =>
sealedTrait.dispatch(x) { sub =>
sub.cast.isDefinedAt(y) && sub.typeclass.eqv(sub.cast(x), sub.cast(y))
}
}
def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] =
Eq.instance(EqMethods.combine(caseClass))

def dispatch[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] =
Eq.instance(EqMethods.dispatch(sealedTrait))

implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T]
}

private object EqMethods {
def combine[T, Typeclass[T] <: Eq[T]](caseClass: CaseClass[Typeclass, T]): (T, T) => Boolean =
(x, y) =>
caseClass.parameters.forall { p =>
p.typeclass.eqv(p.dereference(x), p.dereference(y))
}

def dispatch[T, Typeclass[T] <: Eq[T]](
sealedTrait: SealedTrait[Typeclass, T]
): (T, T) => Boolean =
(x, y) =>
sealedTrait.dispatch(x) { sub =>
sub.cast.isDefinedAt(y) && sub.typeclass.eqv(sub.cast(x), sub.cast(y))
}
}
30 changes: 17 additions & 13 deletions cats/src/main/scala/magnolify/cats/semiauto/GroupDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,23 @@ import scala.language.experimental.macros
object GroupDerivation {
type Typeclass[T] = Group[T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = new Group[T] {
override def inverse(a: T): T = caseClass.construct { p =>
p.typeclass.inverse(p.dereference(a))
}

override val empty: T = caseClass.construct(_.typeclass.empty)

override def combine(x: T, y: T): T = caseClass.construct { p =>
p.typeclass.combine(p.dereference(x), p.dereference(y))
}

override def remove(a: T, b: T): T = caseClass.construct { p =>
p.typeclass.remove(p.dereference(a), p.dereference(b))
def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
val emptyImpl = MonoidMethods.empty(caseClass)
val combineImpl = SemigroupMethods.combine(caseClass)
val combineAllOptionImpl = SemigroupMethods.combineAllOption(caseClass)

new Group[T] {
override def empty: T = emptyImpl
override def combine(x: T, y: T): T = combineImpl(x, y)
override def combineAllOption(as: TraversableOnce[T]): Option[T] = combineAllOptionImpl(as)

override def inverse(a: T): T = caseClass.construct { p =>
p.typeclass.inverse(p.dereference(a))
}

override def remove(a: T, b: T): T = caseClass.construct { p =>
p.typeclass.remove(p.dereference(a), p.dereference(b))
}
}
}

Expand Down
32 changes: 17 additions & 15 deletions cats/src/main/scala/magnolify/cats/semiauto/HashDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@ import scala.util.hashing.MurmurHash3
object HashDerivation {
type Typeclass[T] = Hash[T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = new Hash[T] {
override def hash(x: T): Int =
if (caseClass.parameters.isEmpty) {
caseClass.typeName.short.hashCode
} else {
val seed = MurmurHash3Compat.seed(caseClass.typeName.short.hashCode)
val h = caseClass.parameters.foldLeft(seed) { (h, p) =>
MurmurHash3.mix(h, p.typeclass.hash(p.dereference(x)))
def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
val eqvImpl = EqMethods.combine(caseClass)

new Hash[T] {
override def hash(x: T): Int =
if (caseClass.parameters.isEmpty) {
caseClass.typeName.short.hashCode
} else {
val seed = MurmurHash3Compat.seed(caseClass.typeName.short.hashCode)
val h = caseClass.parameters.foldLeft(seed) { (h, p) =>
MurmurHash3.mix(h, p.typeclass.hash(p.dereference(x)))
}
MurmurHash3.finalizeHash(h, caseClass.parameters.size)
}
MurmurHash3.finalizeHash(h, caseClass.parameters.size)
}

override def eqv(x: T, y: T): Boolean = caseClass.parameters.forall { p =>
p.typeclass.eqv(p.dereference(x), p.dereference(y))
override def eqv(x: T, y: T): Boolean = eqvImpl(x, y)
}
}

Expand All @@ -48,9 +50,9 @@ object HashDerivation {
sub.typeclass.hash(sub.cast(x))
}

override def eqv(x: T, y: T): Boolean = sealedTrait.dispatch(x) { sub =>
sub.cast.isDefinedAt(y) && sub.typeclass.eqv(sub.cast(x), sub.cast(y))
}
private val eqvImpl = EqMethods.dispatch(sealedTrait)

override def eqv(x: T, y: T): Boolean = eqvImpl(x, y)
}

implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T]
Expand Down
20 changes: 14 additions & 6 deletions cats/src/main/scala/magnolify/cats/semiauto/MonoidDerivation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@ object MonoidDerivation {
type Typeclass[T] = Monoid[T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
val empty = caseClass.construct(_.typeclass.empty)
val combine = (x: T, y: T) =>
caseClass.construct { p =>
p.typeclass.combine(p.dereference(x), p.dereference(y))
}
Monoid.instance(empty, combine)
val emptyImpl = MonoidMethods.empty(caseClass)
val combineImpl = SemigroupMethods.combine(caseClass)
val combineAllOptionImpl = SemigroupMethods.combineAllOption(caseClass)

new Monoid[T] {
override def empty: T = emptyImpl
override def combine(x: T, y: T): T = combineImpl(x, y)
override def combineAllOption(as: TraversableOnce[T]): Option[T] = combineAllOptionImpl(as)
}
}

@implicitNotFound("Cannot derive Monoid for sealed trait")
Expand All @@ -40,3 +43,8 @@ object MonoidDerivation {

implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T]
}

private object MonoidMethods {
def empty[T, Typeclass[T] <: Monoid[T]](caseClass: CaseClass[Typeclass, T]): T =
caseClass.construct(_.typeclass.empty)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ import scala.language.experimental.macros
object SemigroupDerivation {
type Typeclass[T] = Semigroup[T]

def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = Semigroup.instance { (x, y) =>
caseClass.construct { p =>
p.typeclass.combine(p.dereference(x), p.dereference(y))
def combine[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = {
val combineImpl = SemigroupMethods.combine(caseClass)
val combineAllOptionImpl = SemigroupMethods.combineAllOption(caseClass)

new Semigroup[T] {
override def combine(x: T, y: T): T = combineImpl(x, y)
override def combineAllOption(as: IterableOnce[T]): Option[T] = combineAllOptionImpl(as)
}
}

Expand All @@ -37,3 +41,32 @@ object SemigroupDerivation {

implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T]
}

private object SemigroupMethods {
def combine[T, Typeclass[T] <: Semigroup[T]](caseClass: CaseClass[Typeclass, T]): (T, T) => T =
(x, y) =>
caseClass.construct { p =>
p.typeclass.combine(p.dereference(x), p.dereference(y))
}

def combineAllOption[T, Typeclass[T] <: Semigroup[T]](
caseClass: CaseClass[Typeclass, T]
): IterableOnce[T] => Option[T] = {
val combineImpl = combine(caseClass)
xs: IterableOnce[T] =>
xs match {
case it: Iterable[T] if it.nonEmpty =>
// input is re-iterable and non-empty, combineAllOption on each field
val result = Array.fill[Any](caseClass.parameters.length)(null)
var i = 0
while (i < caseClass.parameters.length) {
val p = caseClass.parameters(i)
result(i) = p.typeclass.combineAllOption(it.iterator.map(p.dereference)).get
i += 1
}
Some(caseClass.rawConstruct(result))
case xs =>
xs.reduceOption(combineImpl)
}
}
}
16 changes: 10 additions & 6 deletions jmh/src/test/scala/magnolify/jmh/MagnolifyBench.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,22 @@ class ScalaCheckBench {
class CatsBench {
import cats._
import cats.instances.all._
import magnolify.cats.auto._
import magnolify.cats.semiauto._
import MagnolifyBench._
private val integers = implicitly[Arbitrary[Integers]].arbitrary.sample.get
private val sg = implicitly[Semigroup[Integers]]
private val mon = implicitly[Monoid[Integers]]
private val grp = implicitly[Group[Integers]]
private val h = implicitly[Hash[Nested]]
private val e = implicitly[Eq[Nested]]
private val xs = Array.fill(100)(integers)
private val sg = SemigroupDerivation[Integers]
private val mon = MonoidDerivation[Integers]
private val grp = GroupDerivation[Integers]
private val h = HashDerivation[Nested]
private val e = EqDerivation[Nested]
@Benchmark def semigroupCombine: Integers = sg.combine(integers, integers)
@Benchmark def semigroupCombineAllOption: Option[Integers] = sg.combineAllOption(xs)
@Benchmark def monoidCombine: Integers = mon.combine(integers, integers)
@Benchmark def monoidCombineAllOption: Option[Integers] = mon.combineAllOption(xs)
@Benchmark def monoidEmpty: Integers = mon.empty
@Benchmark def groupCombine: Integers = grp.combine(integers, integers)
@Benchmark def groupCombineAllOption: Option[Integers] = grp.combineAllOption(xs)
@Benchmark def groupEmpty: Integers = grp.empty
@Benchmark def groupInverse: Integers = grp.inverse(integers)
@Benchmark def groupRemove: Integers = grp.remove(integers, integers)
Expand Down

0 comments on commit 0f15d0c

Please sign in to comment.