Skip to content

Commit

Permalink
Add CommutativeSemigroup and CommutativeGroup derivation (#221)
Browse files Browse the repository at this point in the history
  • Loading branch information
ceedubs authored Apr 21, 2020
1 parent 93b8ea4 commit 6159d8a
Show file tree
Hide file tree
Showing 8 changed files with 319 additions and 3 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ Instances derivations are available for the following type classes:
* `Foldable`
* `Traverse`
* `Show`
* `Monoid` and `MonoidK`
* `Semigroup` and `SemigroupK`
* `Monoid`, `CommutativeMonoid`, and `MonoidK`
* `Semigroup`, `CommutativeSemigroup`, and `SemigroupK`
* `Empty` (defined in Alleycats)

### Auto derived Examples
Expand Down
57 changes: 57 additions & 0 deletions core/src/main/scala/cats/derived/commutativeMonoid.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2016 Miles Sabin
*
* 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 cats.derived

import cats.kernel.CommutativeMonoid
import shapeless._
import util.VersionSpecific.{OrElse, Lazy}

import scala.annotation.implicitNotFound

@implicitNotFound("""Could not derive an instance of CommutativeMonoid[A] where A = ${A}.
Make sure that A is a case class where all fields have a CommutativeMonoid instance.""")
trait MkCommutativeMonoid[A] extends CommutativeMonoid[A]

object MkCommutativeMonoid extends MkCommutativeMonoidDerivation {
def apply[A](implicit ev: MkCommutativeMonoid[A]): MkCommutativeMonoid[A] = ev
}

private[derived] abstract class MkCommutativeMonoidDerivation {

implicit val mkCommutativeMonoidHNil: MkCommutativeMonoid[HNil] =
instance[HNil](HNil)((_, _) => HNil)

implicit def mkCommutativeMonoidHCons[H, T <: HList](
implicit H: CommutativeMonoid[H] OrElse MkCommutativeMonoid[H], T: MkCommutativeMonoid[T]
): MkCommutativeMonoid[H :: T] = instance(H.unify.empty :: T.empty) {
case (hx :: tx, hy :: ty) => H.unify.combine(hx, hy) :: T.combine(tx, ty)
}


implicit def mkCommutativeMonoidGeneric[A, R](implicit A: Generic.Aux[A, R], R: Lazy[MkCommutativeMonoid[R]]): MkCommutativeMonoid[A] =
new MkCommutativeMonoid[A] {
// Cache empty case classes.
lazy val empty = A.from(R.value.empty)
def combine(x: A, y: A) = A.from(R.value.combine(A.to(x), A.to(y)))
}

private def instance[A](default: => A)(f: (A, A) => A): MkCommutativeMonoid[A] =
new MkCommutativeMonoid[A] {
def empty = default
def combine(x: A, y: A) = f(x, y)
}
}
52 changes: 52 additions & 0 deletions core/src/main/scala/cats/derived/commutativeSemigroup.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright (c) 2016 Miles Sabin
*
* 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 rcommutativesemigroupuired 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 cats.derived

import cats.kernel.CommutativeSemigroup
import shapeless._
import util.VersionSpecific.{OrElse, Lazy}

import scala.annotation.implicitNotFound

@implicitNotFound("""Could not derive an instance of CommutativeSemigroup[A] where A = ${A}.
Make sure that A is a case class where all fields have a CommutativeSemigroup instance.""")
trait MkCommutativeSemigroup[A] extends CommutativeSemigroup[A]

object MkCommutativeSemigroup extends MkCommutativeSemigroupDerivation {
def apply[A](implicit ev: MkCommutativeSemigroup[A]): MkCommutativeSemigroup[A] = ev
}

private[derived] abstract class MkCommutativeSemigroupDerivation {

implicit val mkCommutativeSemigroupHNil: MkCommutativeSemigroup[HNil] =
instance((_, _) => HNil)

implicit def mkCommutativeSemigroupHCons[H, T <: HList](
implicit H: CommutativeSemigroup[H] OrElse MkCommutativeSemigroup[H], T: MkCommutativeSemigroup[T]
): MkCommutativeSemigroup[H :: T] = instance { case (hx :: tx, hy :: ty) =>
H.unify.combine(hx, hy) :: T.combine(tx, ty)
}

implicit def mkCommutativeSemigroupGeneric[A, R](implicit A: Generic.Aux[A, R], R: Lazy[MkCommutativeSemigroup[R]]): MkCommutativeSemigroup[A] =
instance((x, y) => A.from(R.value.combine(A.to(x), A.to(y))))

private def instance[A](f: (A, A) => A): MkCommutativeSemigroup[A] =
new MkCommutativeSemigroup[A] {
def combine(x: A, y: A) = f(x, y)
}
}

29 changes: 29 additions & 0 deletions core/src/main/scala/cats/derived/package.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats
package derived

import cats.kernel.{CommutativeMonoid, CommutativeSemigroup}
import alleycats._
import shapeless.{Cached, Refute}
import util.VersionSpecific.Lazy
Expand Down Expand Up @@ -102,12 +103,24 @@ object auto {
): Semigroup[A] = ev.value
}

object commutativeSemigroup {
implicit def kittensMkCommutativeSemigroup[A](
implicit refute: Refute[CommutativeSemigroup[A]], ev: Lazy[MkCommutativeSemigroup[A]]
): CommutativeSemigroup[A] = ev.value
}

object monoid {
implicit def kittensMkMonoid[A](
implicit refute: Refute[Monoid[A]], ev: Lazy[MkMonoid[A]]
): Monoid[A] = ev.value
}

object commutativeMonoid {
implicit def kittensMkCommutativeMonoid[A](
implicit refute: Refute[CommutativeMonoid[A]], ev: Lazy[MkCommutativeMonoid[A]]
): CommutativeMonoid[A] = ev.value
}

object semigroupK {
implicit def kittensMkSemigroupK[F[_]](
implicit refute: Refute[SemigroupK[F]], F: Lazy[MkSemigroupK[F]]
Expand Down Expand Up @@ -283,12 +296,24 @@ object cached {
): Semigroup[A] = cached.value
}

object commutativeSemigroup {
implicit def kittensMkCommutativeSemigroup[A](
implicit refute: Refute[CommutativeSemigroup[A]], cached: Cached[MkCommutativeSemigroup[A]]
): CommutativeSemigroup[A] = cached.value
}

object monoid {
implicit def kittensMkMonoid[A](
implicit refute: Refute[Monoid[A]], cached: Cached[MkMonoid[A]]
): Monoid[A] = cached.value
}

object commutativeMonoid {
implicit def kittensMkCommutativeMonoid[A](
implicit refute: Refute[CommutativeMonoid[A]], cached: Cached[MkCommutativeMonoid[A]]
): CommutativeMonoid[A] = cached.value
}

object semigroupK {
implicit def kittensMkSemigroupK[F[_]](
implicit refute: Refute[SemigroupK[F]], cached: Cached[MkSemigroupK[F]]
Expand Down Expand Up @@ -369,12 +394,16 @@ object semi {

def monoid[A](implicit ev: Lazy[MkMonoid[A]]): Monoid[A] = ev.value

def commutativeMonoid[A](implicit ev: Lazy[MkCommutativeMonoid[A]]): CommutativeMonoid[A] = ev.value

def monoidK[F[_]](implicit F: Lazy[MkMonoidK[F]]): MonoidK[F] = F.value

def pure[F[_]](implicit F: Lazy[MkPure[F]]): Pure[F] = F.value

def semigroup[T](implicit ev: Lazy[MkSemigroup[T]]): Semigroup[T] = ev.value

def commutativeSemigroup[T](implicit ev: Lazy[MkCommutativeSemigroup[T]]): CommutativeSemigroup[T] = ev.value

def semigroupK[F[_]](implicit F: Lazy[MkSemigroupK[F]]): SemigroupK[F] = F.value

def consK[F[_]](implicit F: Lazy[MkConsK[F, F]]): ConsK[F] = MkConsK.consK(F.value)
Expand Down
16 changes: 16 additions & 0 deletions core/src/test/scala/cats/derived/adtdefns.scala
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,19 @@ object TestDefns {
} yield Foo(i, b))
}

final case class CommutativeFoo(i: Int, b: Option[Long])
object CommutativeFoo {

implicit val cogen: Cogen[CommutativeFoo] =
Cogen[(Int, Option[Long])].contramap(x => (x.i, x.b))

implicit val arbitrary: Arbitrary[CommutativeFoo] =
Arbitrary(for {
i <- Arbitrary.arbitrary[Int]
b <- Arbitrary.arbitrary[Option[Long]]
} yield CommutativeFoo(i, b))
}

case class Inner(i: Int)
case class Outer(in: Inner)

Expand Down Expand Up @@ -448,6 +461,9 @@ object TestEqInstances {
implicit val eqFoo: Eq[Foo] =
Eq.fromUniversalEquals

implicit val eqCommutativeFoo: Eq[CommutativeFoo] =
Eq.fromUniversalEquals

implicit def eqGenericAdt[A: Eq]: Eq[GenericAdt[A]] = {
val eqvOpt = Eq[Option[A]]
Eq.instance { case (GenericAdtCase(vx), GenericAdtCase(vy)) => eqvOpt.eqv(vx, vy) }
Expand Down
80 changes: 80 additions & 0 deletions core/src/test/scala/cats/derived/commutativeMonoid.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2016 Miles Sabin
*
* 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 cats.derived

import cats.kernel.{CommutativeMonoid, CommutativeSemigroup}
import cats.Eq
import cats.instances.all._
import cats.kernel.laws.discipline.{CommutativeMonoidTests, SerializableTests}
import org.scalacheck.Arbitrary

class CommutativeMonoidSuite extends KittensSuite {
import CommutativeMonoidSuite._
import TestDefns._
import TestEqInstances._

def testCommutativeMonoid(context: String)(
implicit commutativeFoo: CommutativeMonoid[CommutativeFoo],
recursive: CommutativeMonoid[Recursive],
box: CommutativeMonoid[Box[Mul]]
): Unit = {
checkAll(s"$context.CommutativeMonoid[Foo]", CommutativeMonoidTests[CommutativeFoo].commutativeMonoid)
checkAll(s"$context.CommutativeMonoid[Recursive]", CommutativeMonoidTests[Recursive].commutativeMonoid)
checkAll(s"$context.CommutativeMonoid[Box[Mul]]", CommutativeMonoidTests[Box[Mul]].commutativeMonoid)
checkAll(s"$context.CommutativeMonoid is Serializable", SerializableTests.serializable(CommutativeMonoid[CommutativeFoo]))

test(s"$context.CommutativeMonoid respects existing instances") {
assert(box.empty == Box(Mul(1)))
assert(box.combine(Box(Mul(5)), Box(Mul(5))) == Box(Mul(25)))
}
}

{
import auto.commutativeMonoid._
testCommutativeMonoid("auto")
}

{
import cached.commutativeMonoid._
testCommutativeMonoid("cached")
}

{
implicit val foo: CommutativeMonoid[CommutativeFoo] = semi.commutativeMonoid
implicit lazy val recursive: CommutativeMonoid[Recursive] = semi.commutativeMonoid
implicit val box: CommutativeMonoid[Box[Mul]] = semi.commutativeMonoid
testCommutativeMonoid("semi")
}
}

object CommutativeMonoidSuite {

final case class Mul(value: Int)
object Mul {

implicit val eqv: Eq[Mul] =
Eq.fromUniversalEquals

implicit val arbitrary: Arbitrary[Mul] =
Arbitrary(Arbitrary.arbitrary[Int].map(apply))

implicit val monoidMul: CommutativeMonoid[Mul] = new CommutativeMonoid[Mul] {
val empty = Mul(1)
def combine(x: Mul, y: Mul) = Mul(x.value * y.value)
}
}
}
82 changes: 82 additions & 0 deletions core/src/test/scala/cats/derived/commutativeSemigroup.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2015 Miles Sabin
*
* 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 cats
package derived

import cats.kernel.CommutativeSemigroup
import cats.implicits._
import cats.kernel.laws.discipline.{CommutativeSemigroupTests, SerializableTests}
import org.scalacheck.Arbitrary

class CommutativeSemigroupSuite extends KittensSuite {
import CommutativeSemigroupSuite._
import TestDefns._
import TestEqInstances._

def testCommutativeSemigroup(context: String)(
implicit foo: CommutativeSemigroup[CommutativeFoo],
recursive: CommutativeSemigroup[Recursive],
box: CommutativeSemigroup[Box[Mul]]
): Unit = {
checkAll(s"$context.CommutativeSemigroup[CommutativeFoo]", CommutativeSemigroupTests[CommutativeFoo].commutativeSemigroup)
checkAll(s"$context.CommutativeSemigroup[Recursive]", CommutativeSemigroupTests[Recursive].commutativeSemigroup)
checkAll(s"$context.CommutativeSemigroup[Box[Mul]]", CommutativeSemigroupTests[Box[Mul]].commutativeSemigroup)
checkAll(s"$context.CommutativeSemigroup is Serializable", SerializableTests.serializable(CommutativeSemigroup[CommutativeFoo]))

test(s"$context.CommutativeSemigroup respects existing instances") {
assert(box.combine(Box(Mul(5)), Box(Mul(5))).content.value == 25)
}
}

{
import auto.commutativeSemigroup._
testCommutativeSemigroup("auto")
}

{
import cached.commutativeSemigroup._
testCommutativeSemigroup("cached")
}

{
implicit val foo: CommutativeSemigroup[CommutativeFoo] = semi.commutativeSemigroup
implicit lazy val recursive: CommutativeSemigroup[Recursive] = semi.commutativeSemigroup
implicit val box: CommutativeSemigroup[Box[Mul]] = semi.commutativeSemigroup
testCommutativeSemigroup("semi")
}
}

object CommutativeSemigroupSuite {

// can be removed once kittens depends on a version of cats that includes https://github.com/typelevel/cats/pull/2834
implicit def commutativeSemigroupOption[A](implicit sa: CommutativeSemigroup[A]): CommutativeSemigroup[Option[A]] = new CommutativeSemigroup[Option[A]] {
def combine(x: Option[A], y: Option[A]): Option[A] = cats.instances.option.catsKernelStdMonoidForOption(sa).combine(x, y)
}

final case class Mul(value: Int)
object Mul {

implicit val eqv: Eq[Mul] =
Eq.fromUniversalEquals

implicit val arbitrary: Arbitrary[Mul] =
Arbitrary(Arbitrary.arbitrary[Int].map(apply))

implicit val commutativeSemigroup: CommutativeSemigroup[Mul] =
CommutativeSemigroup.instance((x, y) => Mul(x.value * y.value))
}
}
2 changes: 1 addition & 1 deletion core/src/test/scala/cats/derived/semigroup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless rsemigroupuired by applicable law or agreed to in writing, software
* 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
Expand Down

0 comments on commit 6159d8a

Please sign in to comment.