Skip to content

Commit

Permalink
Derive NonEmptyAlternative on Scala 3
Browse files Browse the repository at this point in the history
  • Loading branch information
joroKr21 committed Jan 8, 2024
1 parent 076ffce commit 4519742
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 40 deletions.
58 changes: 30 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,37 +337,39 @@ Legend:

#### For monomorphic types

| Type Class | Case Classes | Sealed Traits |
|------------|--------------|---------------|
| CommutativeMonoid | ∀ fields: CommutativeMonoid |
| CommutativeSemigroup | ∀ fields: CommutativeSemigroup |
| Empty | ∀ fields: Empty | ∃ variant: Empty |
| Eq | ∀ fields: Eq | ∀ variants: Eq |
| Hash | ∀ fields: Hash | ∀ variants: Hash |
| Monoid | ∀ fields: Monoid | |
| Order | ∀ fields: Order | ∃! variant: Order |
| PartialOrder | ∀ fields: PartialOrder | ∀ variants: PartialOrder |
| Semigroup | ∀ fields: Semigroup | |
| Show | ∀ fields: Show | ∀ variants: Show |
| ShowPretty | ∀ fields: ShowPretty | ∀ variants: ShowPretty |
| Type Class | Case Classes | Sealed Traits |
|----------------------|--------------------------------|--------------------------|
| CommutativeMonoid | ∀ fields: CommutativeMonoid | |
| CommutativeSemigroup | ∀ fields: CommutativeSemigroup | |
| Empty | ∀ fields: Empty | ∃ variant: Empty |
| Eq | ∀ fields: Eq | ∀ variants: Eq |
| Hash | ∀ fields: Hash | ∀ variants: Hash |
| Monoid | ∀ fields: Monoid | |
| Order | ∀ fields: Order | ∃! variant: Order |
| PartialOrder | ∀ fields: PartialOrder | ∀ variants: PartialOrder |
| Semigroup | ∀ fields: Semigroup | |
| Show | ∀ fields: Show | ∀ variants: Show |
| ShowPretty | ∀ fields: ShowPretty | ∀ variants: ShowPretty |

#### For polymorphic types

| Type Class | Case Classes | Sealed Traits | Constant Types `λ[x => T]` | Nested Types `λ[x => F[G[x]]]` |
|------------|--------------|---------------|----------------------------|--------------------------------|
| Applicative | ∀ fields: Applicative | | for T: Monoid | for F: Applicative and G: Applicative |
| Apply | ∀ fields: Apply | | for T: Semigroup | for F: Apply and G: Apply |
| Contravariant | ∀ fields: Contravariant | ∀ variants: Contravariant | for any T | for F: Functor and G: Contravariant |
| EmptyK | ∀ fields: EmptyK | | for T: Empty | for F: EmptyK and any G ∨ for F: Pure and G: EmptyK |
| Foldable | ∀ fields: Foldable | ∀ variants: Foldable | for any T | for F: Foldable and G: Foldable |
| Functor | ∀ fields: Functor | ∀ variants: Functor | for any T | for F: Functor and G: Functor ∨ for F: Contravariant and G: Contravariant |
| Invariant | ∀ fields: Invariant | ∀ variants: Invariant | for any T | for F: Invariant and G: Invariant |
| MonoidK | ∀ fields: MonoidK | | for T: Monoid | for F: MonoidK and any G ∨ for F: Applicative and G: MonoidK |
| NonEmptyTraverse | ∃ field: NonEmptyTraverse ∧ ∀ fields: Traverse | ∀ variants: NonEmptyTraverse | | for F: NonEmptyTraverse and G: NonEmptyTraverse |
| Pure | ∀ fields: Pure | | for T: Empty | for F: Pure and G: Pure |
| Reducible | ∃ field: Reducible ∧ ∀ fields: Foldable | ∀ variants: Reducible | | for F: Reducible and G: Reducible |
| SemigroupK | ∀ fields: SemigroupK | | for T: Semigroup | for F: SemigroupK and any G ∨ for F: Apply and G: SemigroupK |
| Traverse | ∀ fields: Traverse | ∀ variants: Traverse | for any T | for F: Traverse and G: Traverse |
| Type Class | Case Classes | Sealed Traits | Constant Types `λ[x => T]` | Nested Types `λ[x => F[G[x]]]` |
|-----------------------|--------------------------------------------------|--------------------------------|------------------------------|---------------------------------------------------------------------------|
| Applicative | ∀ fields: Applicative | | for T: Monoid | for F: Applicative and G: Applicative |
| Apply | ∀ fields: Apply | | for T: Semigroup | for F: Apply and G: Apply |
| Contravariant | ∀ fields: Contravariant | ∀ variants: Contravariant | for any T | for F: Functor and G: Contravariant |
| EmptyK | ∀ fields: EmptyK | | for T: Empty | for F: EmptyK and any G ∨ for F: Pure and G: EmptyK |
| Foldable | ∀ fields: Foldable | ∀ variants: Foldable | for any T | for F: Foldable and G: Foldable |
| Functor | ∀ fields: Functor | ∀ variants: Functor | for any T | for F: Functor and G: Functor ∨ for F: Contravariant and G: Contravariant |
| Invariant | ∀ fields: Invariant | ∀ variants: Invariant | for any T | for F: Invariant and G: Invariant |
| MonoidK | ∀ fields: MonoidK | | for T: Monoid | for F: MonoidK and any G ∨ for F: Applicative and G: MonoidK |
| NonEmptyTraverse | ∃ field: NonEmptyTraverse ∧ ∀ fields: Traverse | ∀ variants: NonEmptyTraverse | | for F: NonEmptyTraverse and G: NonEmptyTraverse |
| Pure | ∀ fields: Pure | | for T: Empty | for F: Pure and G: Pure |
| Reducible | ∃ field: Reducible ∧ ∀ fields: Foldable | ∀ variants: Reducible | | for F: Reducible and G: Reducible |
| SemigroupK | ∀ fields: SemigroupK | | for T: Semigroup | for F: SemigroupK and any G ∨ for F: Apply and G: SemigroupK |
| Traverse | ∀ fields: Traverse | ∀ variants: Traverse | for any T | for F: Traverse and G: Traverse |
| **Scala 3 only**|
| NonEmptyAlternative | ∀ fields: NonEmptyAlternative | | | for F: NonEmptyAlternative and G: Applicative |

[cats]: https://github.com/typelevel/cats
[shapeless]: https://github.com/milessabin/shapeless
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package cats.derived

import cats.NonEmptyAlternative
import shapeless3.deriving.K1

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

@implicitNotFound("""Could not derive an instance of NonEmptyAlternative[F] where F = ${F}.
Make sure that F[_] satisfies one of the following conditions:
* it is a nested type [x] =>> G[H[x]] where G: NonEmptyAlternative and H: Applicative
* it is a generic case class where all fields have a NonEmptyAlternative instance""")
type DerivedNonEmptyAlternative[F[_]] = Derived[NonEmptyAlternative[F]]
object DerivedNonEmptyAlternative:
type Or[F[_]] = Derived.Or[NonEmptyAlternative[F]]

@nowarn("msg=unused import")
inline def apply[F[_]]: NonEmptyAlternative[F] =
import DerivedNonEmptyAlternative.given
summonInline[DerivedNonEmptyAlternative[F]].instance

@nowarn("msg=unused import")
inline def strict[F[_]]: NonEmptyAlternative[F] =
import Strict.given
summonInline[DerivedNonEmptyAlternative[F]].instance

given nested[F[_], G[_]](using
F: => Or[F],
G: => DerivedApplicative.Or[G]
): DerivedNonEmptyAlternative[[x] =>> F[G[x]]] =
new Derived.Lazy(() => F.unify.compose(using G.unify)) with NonEmptyAlternative[[x] =>> F[G[x]]]:
export delegate.*

given product[F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedNonEmptyAlternative[F] =
Strict.product(using inst.unify)

trait Product[T[f[_]] <: NonEmptyAlternative[f], F[_]](using K1.ProductInstances[T, F])
extends NonEmptyAlternative[F],
DerivedApplicative.Product[T, F],
DerivedSemigroupK.Product[T, F]

object Strict:
given product[F[_]](using K1.ProductInstances[NonEmptyAlternative, F]): DerivedNonEmptyAlternative[F] =
new Product[NonEmptyAlternative, F]
with DerivedApply.Product[NonEmptyAlternative, F]
with DerivedSemigroupK.Product[NonEmptyAlternative, F] {}
8 changes: 8 additions & 0 deletions core/src/main/scala-3/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ extension (x: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A
extension (x: Show.type) inline def derived[A]: Show[A] = DerivedShow[A]
extension (x: Applicative.type) inline def derived[F[_]]: Applicative[F] = DerivedApplicative[F]
extension (x: Apply.type) inline def derived[F[_]]: Apply[F] = DerivedApply[F]
extension (x: NonEmptyAlternative.type) inline def derived[F[_]]: NonEmptyAlternative[F] = DerivedNonEmptyAlternative[F]
extension (x: EmptyK.type) inline def derived[F[_]]: EmptyK[F] = DerivedEmptyK[F]
extension (x: Pure.type) inline def derived[F[_]]: Pure[F] = DerivedPure[F]
extension (x: Foldable.type) inline def derived[F[_]]: Foldable[F] = DerivedFoldable[F]
Expand All @@ -42,6 +43,7 @@ object semiauto:
inline def commutativeMonoid[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A]
inline def applicative[F[_]]: Applicative[F] = DerivedApplicative[F]
inline def apply[F[_]]: Apply[F] = DerivedApply[F]
inline def nonEmptyAlternative[F[_]]: NonEmptyAlternative[F] = DerivedNonEmptyAlternative[F]
inline def emptyK[F[_]]: EmptyK[F] = DerivedEmptyK[F]
inline def pure[F[_]]: Pure[F] = DerivedPure[F]
inline def foldable[F[_]]: Foldable[F] = DerivedFoldable[F]
Expand Down Expand Up @@ -79,6 +81,8 @@ object strict:
extension (x: Contravariant.type) inline def derived[F[_]]: Contravariant[F] = DerivedContravariant.strict[F]
extension (x: Apply.type) inline def derived[F[_]]: Apply[F] = DerivedApply.strict[F]
extension (x: Applicative.type) inline def derived[F[_]]: Applicative[F] = DerivedApplicative.strict[F]
extension (x: NonEmptyAlternative.type)
inline def derived[F[_]]: NonEmptyAlternative[F] = DerivedNonEmptyAlternative.strict[F]
extension (x: Foldable.type) inline def derived[F[_]]: Foldable[F] = DerivedFoldable.strict[F]
extension (x: Reducible.type) inline def derived[F[_]]: Reducible[F] = DerivedReducible.strict[F]
extension (x: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTraverse.strict[F]
Expand All @@ -105,6 +109,7 @@ object strict:
inline def contravariant[F[_]]: Contravariant[F] = DerivedContravariant.strict[F]
inline def apply[F[_]]: Apply[F] = DerivedApply.strict[F]
inline def applicative[F[_]]: Applicative[F] = DerivedApplicative.strict[F]
inline def nonEmptyAlternative[F[_]]: NonEmptyAlternative[F] = DerivedNonEmptyAlternative.strict[F]
inline def foldable[F[_]]: Foldable[F] = DerivedFoldable.strict[F]
inline def reducible[F[_]]: Reducible[F] = DerivedReducible.strict[F]
inline def traverse[F[_]]: Traverse[F] = DerivedTraverse.strict[F]
Expand Down Expand Up @@ -144,6 +149,9 @@ object auto:
object apply:
inline given [F[_]](using NotGiven[Apply[F]]): Apply[F] = DerivedApply[F]

object nonEmptyAlternative:
inline given [F[_]](using NotGiven[NonEmptyAlternative[F]]): NonEmptyAlternative[F] = DerivedNonEmptyAlternative[F]

object emptyK:
inline given [F[_]](using NotGiven[EmptyK[F]]): EmptyK[F] = DerivedEmptyK[F]

Expand Down
8 changes: 2 additions & 6 deletions core/src/test/scala-3/cats/derived/ApplicativeSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,16 @@

package cats.derived

import cats.{Applicative, Monoid}
import cats.laws.discipline.*
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import cats.{Applicative, Monoid}
import org.scalacheck.Arbitrary
import shapeless3.deriving.Const

import scala.compiletime.*

class ApplicativeSuite extends KittensSuite:
import ApplicativeSuite.*
import ADTs.*

inline given [F[_]]: Isomorphisms[F] =
Isomorphisms.invariant(summonInline[Applicative[F]])
import ApplicativeSuite.*

inline def tests[F[_]]: ApplicativeTests[F] =
ApplicativeTests[F](summonInline)
Expand Down
8 changes: 2 additions & 6 deletions core/src/test/scala-3/cats/derived/ApplySuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,15 @@

package cats.derived

import cats.{Apply, Semigroup}
import cats.laws.discipline.*
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import cats.{Apply, Semigroup}
import shapeless3.deriving.Const

import scala.compiletime.*

class ApplySuite extends KittensSuite:
import ApplySuite.*
import ADTs.*

inline given [F[_]]: Isomorphisms[F] =
Isomorphisms.invariant(summonInline[Apply[F]])
import ApplySuite.*

inline def tests[F[_]]: ApplyTests[F] =
ApplyTests[F](summonInline)
Expand Down
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 @@ -17,12 +17,14 @@
package cats.derived

import cats.Eq
import cats.laws.discipline.SemigroupalTests.Isomorphisms
import cats.platform.Platform
import cats.syntax.AllSyntax
import munit.DisciplineSuite
import org.scalacheck.{Arbitrary, Cogen, Gen}
import org.scalacheck.Test.Parameters

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

Expand Down Expand Up @@ -54,6 +56,7 @@ object KittensSuite:
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)
inline given [F[_]]: Isomorphisms[F] = Isomorphisms.invariant(summonInline)

given [A <: Product](using mirror: Mirror.ProductOf[A], via: Arbitrary[mirror.MirroredElemTypes]): Arbitrary[A] =
Arbitrary(via.arbitrary.map(mirror.fromTuple))
Expand Down
86 changes: 86 additions & 0 deletions core/src/test/scala-3/cats/derived/NonEmptyAlternativeSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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.derived

import cats.laws.discipline.*
import cats.{Applicative, Monoid, NonEmptyAlternative}
import org.scalacheck.Arbitrary
import shapeless3.deriving.Const

import scala.compiletime.*

class NonEmptyAlternativeSuite extends KittensSuite:
import ADTs.*
import NonEmptyAlternativeSuite.*

inline def tests[F[_]]: NonEmptyAlternativeTests[F] =
NonEmptyAlternativeTests[F](summonInline)

inline def validate(inline instance: String): Unit =
checkAll(s"$instance[CaseClassWOption]", tests[CaseClassWOption].nonEmptyAlternative[Int, String, Long])
checkAll(s"$instance[OptList]", tests[OptList].nonEmptyAlternative[Int, String, Long])
checkAll(s"$instance[UnCons]", tests[UnCons].nonEmptyAlternative[Int, String, Long])
checkAll(
s"$instance is Serializable",
SerializableTests.serializable(summonInline[NonEmptyAlternative[CaseClassWOption]])
)

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

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

locally:
import strictInstances.given
validate("strict.semiauto.nonEmptyAlternative")
testNoInstance("strict.semiauto.nonEmptyAlternative", "TopK")

locally:
import derivedInstances.*
val instance = "derived.nonEmptyAlternative"
checkAll(s"$instance[CaseClassWOption]", tests[CaseClassWOption].nonEmptyAlternative[Int, String, Long])
checkAll(s"$instance[UnCons]", tests[UnCons].nonEmptyAlternative[Int, String, Long])
checkAll(s"$instance is Serializable", SerializableTests.serializable(Applicative[CaseClassWOption]))

end NonEmptyAlternativeSuite

object NonEmptyAlternativeSuite:
import ADTs.*

type OptList[A] = Option[List[A]]
type UnCons[+A] = (Option[A], Vector[A])

object semiInstances:
given NonEmptyAlternative[CaseClassWOption] = semiauto.nonEmptyAlternative
given NonEmptyAlternative[OptList] = semiauto.nonEmptyAlternative
given NonEmptyAlternative[UnCons] = semiauto.nonEmptyAlternative

object strictInstances:
given [T: Monoid]: NonEmptyAlternative[Const[T]] = semiauto.nonEmptyAlternative
given [F[_]: NonEmptyAlternative, G[_]: Applicative]: NonEmptyAlternative[[x] =>> F[G[x]]] =
NonEmptyAlternative[F].compose[G]
given NonEmptyAlternative[CaseClassWOption] = strict.semiauto.nonEmptyAlternative
given NonEmptyAlternative[UnCons] = strict.semiauto.nonEmptyAlternative

object derivedInstances:
case class CaseClassWOption[A](x: ADTs.CaseClassWOption[A]) derives NonEmptyAlternative
case class UnCons[A](x: NonEmptyAlternativeSuite.UnCons[A]) derives NonEmptyAlternative

end NonEmptyAlternativeSuite

0 comments on commit 4519742

Please sign in to comment.