Skip to content

Commit

Permalink
Derive Group on Scala 3 (#638)
Browse files Browse the repository at this point in the history
* Derive Group on Scala 3

* Add a spec for DerivedGroup
  • Loading branch information
joroKr21 authored Jan 20, 2024
1 parent 6be1590 commit 2fd70dc
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 1 deletion.
33 changes: 33 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedGroup.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cats.derived

import cats.Group
import shapeless3.deriving.K0.*

import scala.annotation.*
import scala.compiletime.*

@implicitNotFound("""Could not derive an instance of Group[A] where A = ${A}.
Make sure that A is a case class where all fields have a Group instance.""")
type DerivedGroup[A] = Derived[Group[A]]
object DerivedGroup:
type Or[A] = Derived.Or[Group[A]]

@nowarn("msg=unused import")
inline def apply[A]: Group[A] =
import DerivedGroup.given
summonInline[DerivedGroup[A]].instance

@nowarn("msg=unused import")
inline def strict[A]: Group[A] =
import Strict.given
summonInline[DerivedGroup[A]].instance

given [A](using inst: => ProductInstances[Or, A]): DerivedGroup[A] =
Strict.product(using inst.unify)

trait Product[F[x] <: Group[x], A](using inst: ProductInstances[F, A]) extends DerivedMonoid.Product[F, A], Group[A]:
override def inverse(a: A): A = inst.map(a)([a] => (F: F[a], x: a) => F.inverse(x))

object Strict:
given product[A: ProductInstancesOf[Group]]: DerivedGroup[A] =
new Product[Group, A] {}
2 changes: 1 addition & 1 deletion core/src/main/scala-3/cats/derived/DerivedMonoid.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ object DerivedMonoid:
extends DerivedSemigroup.Product[F, A],
Monoid[A]:
final override lazy val empty: A =
inst.construct([A] => (F: F[A]) => F.empty)
inst.construct([a] => (F: F[a]) => F.empty)

object Strict:
given product[A: ProductInstancesOf[Monoid]]: DerivedMonoid[A] =
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala-3/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ extension (x: Hash.type) inline def derived[A]: Hash[A] = DerivedHash[A]
extension (x: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty[A]
extension (x: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup[A]
extension (x: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid[A]
extension (x: Group.type) inline def derived[A]: Group[A] = DerivedGroup[A]
extension (x: Order.type) inline def derived[A]: Order[A] = DerivedOrder[A]
extension (x: CommutativeSemigroup.type) inline def derived[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A]
extension (x: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A]
Expand Down Expand Up @@ -39,6 +40,7 @@ object semiauto:
inline def empty[A]: Empty[A] = DerivedEmpty[A]
inline def semigroup[A]: Semigroup[A] = DerivedSemigroup[A]
inline def monoid[A]: Monoid[A] = DerivedMonoid[A]
inline def group[A]: Group[A] = DerivedGroup[A]
inline def order[A]: Order[A] = DerivedOrder[A]
inline def commutativeSemigroup[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A]
inline def commutativeMonoid[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A]
Expand Down Expand Up @@ -71,6 +73,7 @@ object strict:
extension (x: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty.strict[A]
extension (x: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup.strict[A]
extension (x: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid.strict[A]
extension (x: Group.type) inline def derived[A]: Group[A] = DerivedGroup.strict[A]
extension (x: CommutativeSemigroup.type)
inline def derived[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup.strict[A]
extension (x: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid.strict[A]
Expand Down Expand Up @@ -101,6 +104,7 @@ object strict:
inline def empty[A]: Empty[A] = DerivedEmpty.strict[A]
inline def semigroup[A]: Semigroup[A] = DerivedSemigroup.strict[A]
inline def monoid[A]: Monoid[A] = DerivedMonoid.strict[A]
inline def group[A]: Group[A] = DerivedGroup.strict[A]
inline def commutativeSemigroup[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup.strict[A]
inline def commutativeMonoid[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid.strict[A]
inline def emptyK[F[_]]: EmptyK[F] = DerivedEmptyK.strict[F]
Expand Down Expand Up @@ -138,6 +142,9 @@ object auto:
object monoid:
inline given [A: NotGivenA[Monoid]]: Monoid[A] = DerivedMonoid[A]

object group:
inline given [A: NotGivenA[Group]]: Group[A] = DerivedGroup[A]

object order:
inline given [A: NotGivenA[Order]]: Order[A] = DerivedOrder[A]

Expand Down
74 changes: 74 additions & 0 deletions core/src/test/scala-3/cats/derived/GroupSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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.Group
import cats.kernel.laws.discipline.{GroupTests, SerializableTests}

import scala.compiletime.*
import scala.concurrent.duration.Duration

class GroupSuite extends KittensSuite:
import GroupSuite.*

inline def tests[A]: GroupTests[A] =
GroupTests[A](using summonInline)

inline def validate(inline instance: String): Unit =
checkAll(s"$instance[Slice]", tests[Slice].group)
checkAll(s"$instance[Compared]", tests[Compared].group)
checkAll(s"$instance is Serializable", SerializableTests.serializable(summonInline[Group[Slice]]))

locally:
import auto.group.given
validate("auto.group")

locally:
import semiInstances.given
validate("semiauto.group")

locally:
import strictInstances.given
validate("strict.semiauto.group")
testNoInstance("strict.semiauto.group", "Top")

locally:
import derivedInstances.*
val instance = "derived.group"
checkAll(s"$instance[Slice]", tests[Slice].group)
checkAll(s"$instance[Compared]", tests[Compared].group)
checkAll(s"$instance is Serializable", SerializableTests.serializable(Group[Slice]))

end GroupSuite

object GroupSuite:
case class Slice(count: Long, percentile: Double, duration: Duration)
case class Compared(x: Slice, y: Slice)

object semiInstances:
given Group[Slice] = semiauto.group
given Group[Compared] = semiauto.group

object strictInstances:
given Group[Slice] = strict.semiauto.group
given Group[Compared] = strict.semiauto.group

object derivedInstances:
case class Slice(x: GroupSuite.Slice) derives Group
case class Compared(x: GroupSuite.Compared) derives Group

end GroupSuite
3 changes: 3 additions & 0 deletions core/src/test/scala-3/cats/derived/KittensSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.scalacheck.{Arbitrary, Cogen, Gen}
import org.scalacheck.Test.Parameters

import scala.compiletime.summonInline
import scala.concurrent.duration.*
import scala.deriving.Mirror
import scala.quoted.*

Expand Down Expand Up @@ -53,6 +54,8 @@ object KittensSuite:
.withMaxSize(if Platform.isJvm then 10 else 5)
.withMinSize(0)

// The default Arbitrary[Duration] causes overflow.
given Arbitrary[Duration] = Arbitrary(Gen.chooseNum(-1000.days.toNanos, 1000.days.toNanos).map(Duration.fromNanos))
given [A: Arbitrary]: Arbitrary[List[A]] = Arbitrary.arbContainer
given [A <: Singleton: ValueOf]: Arbitrary[A] = Arbitrary(Gen.const(valueOf[A]))
given [A <: Singleton: ValueOf]: Cogen[A] = Cogen((seed, _) => seed)
Expand Down

0 comments on commit 2fd70dc

Please sign in to comment.