Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Derive Alternative on Scala 3 #630

Merged
merged 2 commits into from
Jan 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 56 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ service][sonatype] and synced to Maven Central.

It is available for Scala 2.12 and 2.13, Scala.js 1.5 and Scala Native 0.4.

To get started with sbt, simply add the following to your `build.sbt` file:
To get started with sbt, add the following to your `build.sbt` file:

```Scala
libraryDependencies += "org.typelevel" %% "kittens" % "latestVersion" // indicated in the badge below
Expand Down Expand Up @@ -84,13 +84,13 @@ scala> mike.show
res0: String = People(name = Mike, contactInfo = ContactInfo(phoneNumber = 202-295-3928, address = 1 Main ST, Chicago, IL))
```

Note that in this example, the derivation generated instances for all referenced classes but still respected the
existing instance in scope. For different ways to derive instances please see the
[three modes of derivation below](#three-modes-of-derivation).
Note that in this example,
the derivation generated instances for all referenced classes but still respected the existing instance in scope.
For different ways to derive instances, please see the [three modes of derivation below](#three-modes-of-derivation).

### Sequence examples

Note that to run these examples you need partial unification enabled.
Note that to run these examples, you need partial unification enabled.
For **Scala 2.12** you should add the following to your `build.sbt`:

```scala
Expand Down Expand Up @@ -152,8 +152,8 @@ res0: Option[String] = Some(1 - a - 3.2)

### Three modes of derivation

Kittens provides three objects for derivation `cats.derived.auto`, `cats.derived.cached` and `cats.derived.semi`
The recommended best practice is going to be a semi auto one:
Kittens provides three objects for derivation `cats.derived.auto`, `cats.derived.cached` and `cats.derived.semi`.
The recommended best practice is the semiauto one:

```scala
import cats.derived
Expand All @@ -167,7 +167,7 @@ This will respect all existing instances even if the field is a type constructor
the native `Show` instance for `List` and derived instance for `A`. And it manually caches the result to the
`val showFoo`. Downside user will need to write one for every type they directly need a `Show` instance.

There are 3 alternatives:
There are three alternatives:
1. full auto:

```scala
Expand All @@ -184,18 +184,19 @@ import derived.cached.show._
```

Use this one with caution. It caches the derived instance globally. So it's only applicable if the instance is global
in the application. This could be problematic for libraries, which has no control over the uniqueness of an instance on
in the application. This could be problematic for libraries, which have no control over the uniqueness of an instance on
use site. It relies on `shapeless.Cached` which is buggy.

3. manual semi
```scala
implicit val showFoo: Show[Foo] = derived.semiauto.show
```

It has the same downside as the recommenced semi-auto practice but also suffers from the type constructor field issue.
I.e. if a field type is a type constructor whose native instance relies on the instance of the parameter type, this
approach will by default derive an instance for the type constructor one. To overcome this user have to first derive
the instance for type parameter. e.g. given:
It has the same downside as the recommenced semiauto practice but also suffers from the type constructor field issue.
I.e., if a field type is a type constructor whose native instance relies on the instance of the parameter type,
this approach will by default derive an instance for the type constructor one.
To overcome this, user have to first derive the instance for type parameter.
E.g., given:

```scala
case class Foo(bars: List[Bar])
Expand All @@ -214,9 +215,10 @@ This way the native instance for `Show[List]` would be used.

## Scala 3

We also offer 3 methods of derivation for Scala 3. All of them have the same behaviour wrt to recursively defining instances:
We also offer three methods of derivation for Scala 3.
All of them have the same behavior wrt to recursively defining instances:
1. Instances will always be recursively instantiated if necessary
2. Subject to the same type constructor field limitation as the Scala 2 auto and manual semi derivations
2. Subject to the same type constructor field limitation as the Scala 2 auto and manual semiauto derivations

### `derives` syntax (recommended)

Expand Down Expand Up @@ -314,16 +316,18 @@ List[Set[x]]]`.

#### Stack safety

Our derived instances are not stack-safe. This is a departure from the behaviour for Scala 2 because we didn't want to incur the performance penalty of trampolining all instances in `cats.Eval`. If your data-type is recursive or _extremely_ large then you may want to write instances by hand instead.
Our derived instances are not stack-safe.
This is a departure from the behaviour for Scala 2
because we didn't want to incur the performance penalty of trampolining all instances in `cats.Eval`.
If your data-type is recursive or _extremely_ large, then you may want to write instances by hand instead.

#### Missing features

Kittens for Scala 3 is built on top of [Shapeless
3](https://github.com/typelevel/shapeless-3) which has a completely different
API than [Shapeless 2](https://github.com/milessabin/shapeless) so we don't
support features like `Sequence` and `Lift`.
Kittens for Scala 3 is built on top of [Shapeless 3](https://github.com/typelevel/shapeless-3)
which has a completely different API than [Shapeless 2](https://github.com/milessabin/shapeless),
so we don't support features like `Sequence` and `Lift`.

`ConsK` derivation is also not supported although we expect this to be
`ConsK` derivation is also not supported, although we expect this to be
[added](https://github.com/typelevel/kittens/issues/489) in a future release.

## Type class support matrix
Expand All @@ -337,39 +341,40 @@ 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 | ∀ variants: 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 | Singleton types |
|----------------------|--------------------------------|--------------------------|:---------------:|
| 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 | ∀ variants: 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 | ∃! variant: 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 |
| 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 | ∃! variant: 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 |
| Alternative | ∀ fields: Alternative | ✗ | ✗ | for F: Alternative and G: Applicative |

[cats]: https://github.com/typelevel/cats
[shapeless]: https://github.com/milessabin/shapeless
Expand Down
44 changes: 44 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedAlternative.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cats.derived

import cats.Alternative
import shapeless3.deriving.K1

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

@implicitNotFound("""Could not derive an instance of Alternative[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: Alternative and H: Applicative
* it is a generic case class where all fields have a Alternative instance""")
type DerivedAlternative[F[_]] = Derived[Alternative[F]]
object DerivedAlternative:
type Or[F[_]] = Derived.Or[Alternative[F]]

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

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

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

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

trait Product[T[f[_]] <: Alternative[f], F[_]](using K1.ProductInstances[T, F])
extends Alternative[F],
DerivedNonEmptyAlternative.Product[T, F],
DerivedMonoidK.Product[T, F]

object Strict:
given product[F[_]](using K1.ProductInstances[Alternative, F]): DerivedAlternative[F] =
new Alternative[F] with Product[Alternative, F] {}
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ object DerivedApplicative:

object Strict:
given product[F[_]](using K1.ProductInstances[Applicative, F]): DerivedApplicative[F] =
new Product[Applicative, F] with DerivedApply.Product[Applicative, F] {}
new Applicative[F] with Product[Applicative, F] {}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,4 @@ object DerivedNonEmptyAlternative:

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] {}
new NonEmptyAlternative[F] with Product[NonEmptyAlternative, F] {}
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,4 @@ object DerivedNonEmptyTraverse:

given coproduct[F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedNonEmptyTraverse[F] =
given K1.CoproductInstances[NonEmptyTraverse, F] = inst.unify
new Coproduct[NonEmptyTraverse, F]
with DerivedReducible.Coproduct[NonEmptyTraverse, F]
with DerivedTraverse.Coproduct[NonEmptyTraverse, F]
with DerivedFunctor.Generic[NonEmptyTraverse, F] {}
new NonEmptyTraverse[F] with Coproduct[NonEmptyTraverse, F] {}
4 changes: 2 additions & 2 deletions core/src/main/scala-3/cats/derived/DerivedTraverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ object DerivedTraverse:

object Strict:
given product[F[_]](using K1.ProductInstances[Traverse, F]): DerivedTraverse[F] =
new Product[Traverse, F] with DerivedFunctor.Generic[Traverse, F] {}
new Traverse[F] with Product[Traverse, F] {}

given coproduct[F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedTraverse[F] =
given K1.CoproductInstances[Traverse, F] = inst.unify
new Coproduct[Traverse, F] with DerivedFunctor.Generic[Traverse, F] {}
new Traverse[F] with Coproduct[Traverse, F] {}
Loading