-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CommutativeSemigroup and CommutativeGroup derivation (#221)
- Loading branch information
Showing
8 changed files
with
319 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
52
core/src/main/scala/cats/derived/commutativeSemigroup.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
82
core/src/test/scala/cats/derived/commutativeSemigroup.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters