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

Scala 3 support #514

Merged
merged 59 commits into from
Sep 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
dbac100
Setup build for Scala 3
joroKr21 May 7, 2021
826adb7
Hello Scala 3 + Shapeless 3
joroKr21 May 7, 2021
0de4b02
For now use test instead of validate
joroKr21 May 7, 2021
ad2efa6
Use the correct source directory
joroKr21 May 7, 2021
c94bdf2
Fix version specific source directories (#331)
joroKr21 May 12, 2021
eae71f2
Derive Functor, Foldable, Traverse (#330)
TimWSpence May 17, 2021
cd6baa2
Derive Eq, PartialOrder, Order and Hash (#336)
TimWSpence May 18, 2021
803b582
Derive Commutative{Semigroup, Monoid} (#341)
TimWSpence May 19, 2021
3d864d9
Derive Show with field labels (#340)
TimWSpence May 19, 2021
dd422a3
Derive MonoidK hierarchy (#342)
TimWSpence May 20, 2021
7ff0189
Scala 3.0, Shapeless 3.0 and other dependencies (#343)
joroKr21 May 21, 2021
970ec37
Cleanup Const - it's provided by shapeless (#345)
joroKr21 May 21, 2021
9ffcece
Derive Contravariant and Invariant (#346)
TimWSpence May 24, 2021
a033a34
Reducible derivation (#347)
joroKr21 Jun 1, 2021
6b5154d
Deep and Coproduct Empty derivation (#349)
joroKr21 Jun 1, 2021
851c322
Use summonFrom to prioritize DerivedEmpty instances (#355)
bplommer Jun 2, 2021
d4419da
Further simplify Empty derivation (#356)
bplommer Jun 2, 2021
b13bb1e
Generic Derived opaque type for given priority (#359)
joroKr21 Jun 3, 2021
73d3be4
semiauto and auto imports (#353)
TimWSpence Jun 4, 2021
5b3b7ca
Update sbt and shapeless, use project to derive Show for products (#363)
joroKr21 Jun 7, 2021
f2bca9a
Port functor tests (#362)
TimWSpence Aug 20, 2021
b4200bf
Port FoldableSuite to Scala 3 (#374)
joroKr21 Aug 29, 2021
c68362d
Port ReducibleSuite to Scala 3 (#379)
joroKr21 Aug 31, 2021
2a0d561
Port TraverseSuite to Scala 3 (#377)
joroKr21 Sep 26, 2021
59568a0
Port EmptySuite and also solve Empty for Coproducts (#387)
joroKr21 Sep 26, 2021
7ac8008
Port tests for Semigroup and Monoid (#389)
joroKr21 Sep 26, 2021
ea0c0ca
Port Eq and Hash plus tests (#393)
joroKr21 Oct 10, 2021
b2a1fef
Port NonEmptyTraverse derivation to Scala 3 (#394)
joroKr21 Oct 11, 2021
873ae0d
Make derived methods final override (#395)
joroKr21 Oct 12, 2021
293813d
Bump Scala and Cats and comment out tests that don't work (#427)
joroKr21 Jan 14, 2022
d43755f
Port Show with tests (#433)
andrzejressel Feb 6, 2022
5896436
Port Apply to Dotty (#441)
andrzejressel Feb 9, 2022
56d551d
Port Order to Dotty (#440)
andrzejressel Feb 13, 2022
52646a3
Port Applicative to Dotty (#439)
andrzejressel Feb 13, 2022
2487d20
Merge master to dotty branch (#446)
joroKr21 Feb 13, 2022
f1b74cd
Update to Scala 3.1.2-RC3 and fix outstanding issues (#455)
joroKr21 Mar 25, 2022
735d569
Scala 3.1.2 (#464)
joroKr21 Apr 13, 2022
9c9123e
Port EmptyK to dotty (#448)
andrzejressel Apr 16, 2022
cd50c1b
Fix ApplicativeSuite (#465)
joroKr21 Apr 16, 2022
2965be9
Shapeless 3.1.0 (#468)
joroKr21 May 22, 2022
5dc1983
Derive Pure for Scala 3 (#470)
joroKr21 May 22, 2022
8f76c24
Add missing implicit not found messages (#471)
joroKr21 May 22, 2022
55bad35
Port Semigroupk/Monoidk to new scheme (#472)
TimWSpence May 24, 2022
1096075
Port contravariant to new scheme (#475)
TimWSpence May 25, 2022
5de791e
Port invariant to new scheme (#478)
TimWSpence May 29, 2022
21c41a3
Port partial order to new scheme (#480)
TimWSpence May 30, 2022
bdd47fa
Require Or instances for givens (#486)
TimWSpence May 30, 2022
eec95c2
Scala 3 all the tests (#485)
TimWSpence May 30, 2022
f043dda
Remove obsolete Instances (#487)
joroKr21 May 30, 2022
522083f
Derive ShowPretty (#490)
TimWSpence Jun 6, 2022
a68066c
Fix ShowPretty tests
TimWSpence Jun 10, 2022
ed10894
Nicer syntax
TimWSpence Jun 10, 2022
de620e8
Merge pull request #497 from TimWSpence/fix-tests
TimWSpence Jun 10, 2022
0bad32b
Test scala 3 enums (#498)
TimWSpence Jun 11, 2022
72064a2
Cross-build for Native (#501)
armanbilge Jun 17, 2022
e2420ce
Upgrade to Scala 3.2 (#513)
TimWSpence Sep 9, 2022
55682d1
Docs (#509)
TimWSpence Sep 9, 2022
2db5019
Merge branch 'master' into dotty
joroKr21 Sep 9, 2022
a9c8689
Fix and simplify ApplySuite
joroKr21 Sep 12, 2022
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
34 changes: 32 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.12.16, 2.13.8]
scala: [2.12.16, 2.13.8, 3.2.0]
java: [temurin@8]
project: [rootJS, rootJVM, rootNative]
runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -114,7 +114,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.13.8]
scala: [3.2.0]
java: [temurin@8]
runs-on: ${{ matrix.os }}
steps:
Expand Down Expand Up @@ -211,6 +211,36 @@ jobs:
tar xf targets.tar
rm targets.tar

- name: Download target directories (3.2.0, rootJS)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootJS

- name: Inflate target directories (3.2.0, rootJS)
run: |
tar xf targets.tar
rm targets.tar

- name: Download target directories (3.2.0, rootJVM)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootJVM

- name: Inflate target directories (3.2.0, rootJVM)
run: |
tar xf targets.tar
rm targets.tar

- name: Download target directories (3.2.0, rootNative)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootNative

- name: Inflate target directories (3.2.0, rootNative)
run: |
tar xf targets.tar
rm targets.tar

- name: Import signing key
if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == ''
run: echo $PGP_SECRET | base64 -di | gpg --import
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ metals.sbt
.*.swp
.swo
.*.swo

# VS Code
.bloop
.bsp
.metals
.vscode
**/metals.sbt
3 changes: 3 additions & 0 deletions .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ pull_request_rules:
- status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8, rootJS)
- status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8, rootJVM)
- status-success=Build and Test (ubuntu-latest, 2.13.8, temurin@8, rootNative)
- status-success=Build and Test (ubuntu-latest, 3.2.0, temurin@8, rootJS)
- status-success=Build and Test (ubuntu-latest, 3.2.0, temurin@8, rootJVM)
- status-success=Build and Test (ubuntu-latest, 3.2.0, temurin@8, rootNative)
actions:
merge: {}
- name: Label core PRs
Expand Down
5 changes: 4 additions & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ rewrite.redundantBraces.stringInterpolation = true
rewrite.redundantBraces.methodBodies = true
rewrite.redundantBraces.generalExpressions = true
rewriteTokens = { "⇒": "=>", "→": "->", "←": "<-" }
fileOverride { "glob:**/*.sbt" { runner.dialect = scala212 } }
fileOverride {
"glob:**/*.sbt" { runner.dialect = scala212 },
"glob:**/scala-3/**" { runner.dialect = scala3 }
}
107 changes: 106 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ libraryDependencies += "org.typelevel" %% "kittens" % "latestVersion" // indicat
[![Scala.js](http://scala-js.org/assets/badges/scalajs-1.5.0.svg)](http://scala-js.org)
[![Latest version](https://img.shields.io/maven-central/v/org.typelevel/kittens_2.12.svg)](https://maven-badges.herokuapp.com/maven-central/org.typelevel/kittens_2.12)

## Scala 2

Instance derivations are available for the following type classes:

* `Eq`, `PartialOrder`, `Order`, `Hash`
Expand Down Expand Up @@ -210,7 +212,109 @@ implicit val showFoo: Show[Foo] = semiauto.show

This way the native instance for `Show[List]` would be used.

### Type class support matrix
## Scala 3

We also offer 3 methods of derivation for Scala 3. All of them have the same behaviour 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

### `derives` syntax (recommended)

Kittens for scala 3 supports Scala 3's [derivation syntax](https://docs.scala-lang.org/scala3/reference/contextual/derivation.html).

``` scala
import cats.derived.*

// No instances declared for Name
case class Name(value: String)
case class Person(name: Name, age: Int) derives Eq, Show

enum CList[+A] derives Functor:
case CNil
case CCons(head: A, tail: CList[A])
```

### semiauto derivation

This looks similar to `semiauto` for Scala 2.

``` scala
import cats.derived.semiauto

// No instances declared for Name
case class Name(value: String)
case class Person(name: Name, age: Int)

object Person:
given Eq[Person] = semiauto.eq
given Show[Person] = semiauto.show

enum CList[+A]:
case CNil
case CCons(head: A, tail: CList[A])

object CList:
given Functor[CList] = semiauto.functor
```

As with Scala 2, you can combine `auto` and `semiauto` to avoid the type constructor field limitation:

``` scala
import cats.derived.*

case class Name(value: String)
case class Person(name: Name, age: Int)

case class People(people: List[Person])
object People:
given Show[People] =
import auto.show.given
// Uses the correct List instance despite deriving an instance for Person automatically
semiauto.show
```

`

### auto derivation

This looks similar to `auto` for Scala 2.

``` scala
import cats.derived.auto.eq.given
import cats.derived.auto.show.given
import cats.derived.auto.functor.given

case class Name(value: String)
case class Person(name: Name, age: Int)

enum CList[+A]:
case CNil
case CCons(head: A, tail: CList[A])
```

### Caveats

#### Nested type constructors

We are [currently](https://github.com/typelevel/kittens/issues/473) unable to
derive instances for nested type constructors, such as `Functor[[x] =>>
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.

#### 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`.

`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

Legend:
- `∀` - all must satisfy a constraint
Expand Down Expand Up @@ -277,4 +381,5 @@ Kittens is built with SBT 1.x, and its master branch is built with Scala 2.13 by
+ Miles Sabin <miles@milessabin.com> [@milessabin](https://twitter.com/milessabin)
+ Qi Wang [Qi77Qi](http://github.com/Qi77Qi)
+ Kailuo Wang <kailuo.wang@gmail.com> [@kailuowang](https://twitter.com/kailuowang)
+ Tim Spence <timothywspence@gmail.com> [timwspence](https://twitter.com/timwspence)
+ Your name here :-)
49 changes: 31 additions & 18 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,42 +1,54 @@
import sbt._

ThisBuild / crossScalaVersions := Seq("2.12.16", "2.13.8")
ThisBuild / scalaVersion := "2.13.8"
ThisBuild / tlBaseVersion := "2.3"
val scala212 = "2.12.16"
val scala213 = "2.13.8"
val scala3 = "3.2.0"

ThisBuild / crossScalaVersions := Seq(scala212, scala213, scala3)
ThisBuild / scalaVersion := scala3
ThisBuild / tlBaseVersion := "3.0"
ThisBuild / organization := "org.typelevel"

val catsVersion = "2.8.0"
val disciplineMunitVersion = "1.0.9"
val munitVersion = "1.0.0-M6"
val disciplineMunitVersion = "2.0.0-M3"
val kindProjectorVersion = "0.13.2"
val shapelessVersion = "2.3.9"
val shapeless2Version = "2.3.8"
val shapeless3Version = "3.1.0"

lazy val commonSettings = Seq(
scalacOptions := Seq(
scalacOptions ++= Seq(
"-feature",
"-language:higherKinds",
"-language:implicitConversions",
"-unchecked",
"-deprecation",
"-Xfatal-warnings"
),
scalacOptions ++= (
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, v)) if v <= 12 => Seq("-Ypartial-unification")
case _ => Seq.empty
}
),
scalacOptions ++= CrossVersion.partialVersion(scalaVersion.value).toList.flatMap {
case (3, _) => List("-Xmax-inlines", "64")
case (2, 12) => List("-Ypartial-unification")
case _ => Nil
},
resolvers ++= Resolver.sonatypeOssRepos("releases"),
resolvers ++= Resolver.sonatypeOssRepos("snapshots"),
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % catsVersion,
"org.typelevel" %%% "alleycats-core" % catsVersion,
"com.chuusai" %%% "shapeless" % shapelessVersion,
"org.typelevel" %%% "cats-testkit" % catsVersion % Test,
"org.scalameta" %%% "munit" % "0.7.29" % Test,
"org.typelevel" %%% "discipline-munit" % disciplineMunitVersion % Test,
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Test,
compilerPlugin(("org.typelevel" %% "kind-projector" % kindProjectorVersion).cross(CrossVersion.full))
"org.scalameta" %%% "munit" % munitVersion % Test
),
libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) =>
Seq("org.typelevel" %%% "shapeless3-deriving" % shapeless3Version)
case _ =>
Seq(
"com.chuusai" %%% "shapeless" % shapeless2Version,
"org.scala-lang" % "scala-reflect" % scalaVersion.value % Test,
compilerPlugin(("org.typelevel" %% "kind-projector" % kindProjectorVersion).cross(CrossVersion.full))
)
}),
Test / parallelExecution := false
)

Expand Down Expand Up @@ -74,14 +86,15 @@ ThisBuild / licenses := Seq(License.Apache2)
ThisBuild / developers := List(
Developer("milessabin", "Miles Sabin", "", url("http://milessabin.com/blog")),
Developer("kailuowang", "Kai(luo) Wang", "kailuo.wang@gmail.com", url("http://kailuowang.com/")),
Developer("joroKr21", "Georgi Krastev", "joro.kr.21@gmail.com", url("https://twitter.com/Joro_Kr"))
Developer("joroKr21", "Georgi Krastev", "joro.kr.21@gmail.com", url("https://twitter.com/Joro_Kr")),
Developer("TimWSpence", "Tim Spence", "timothywspence@gmail.com", url("https://twitter.com/timwspence"))
)

ThisBuild / tlCiScalafmtCheck := true
ThisBuild / tlCiReleaseBranches := Seq("master")
ThisBuild / mergifyStewardConfig := Some(
MergifyStewardConfig(
author = "typelevel-steward[bot]",
mergeMinors = true
)
)
ThisBuild / tlCiScalafmtCheck := true
36 changes: 36 additions & 0 deletions core/src/main/scala-3/cats/derived/Derived.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cats.derived

import shapeless3.deriving.*
import scala.annotation.*
import scala.compiletime.*

@implicitNotFound("Could not derive an instance of ${A}")
opaque type Derived[A] = A
object Derived:
def apply[A](instance: A): Derived[A] = instance
extension [A](derived: Derived[A]) def instance: A = derived
given [A]: Conversion[A, Derived[A]] = apply

type Or0[F[_]] = [x] =>> Or[F[x]]
type Or1[F[_[_]]] = [x[_]] =>> Or[F[x]]
type Or11[F[_[_[_]]]] = [x[_[_]]] =>> Or[F[x]]
type Or2[F[_[_, _]]] = [x[_, _]] =>> Or[F[x]]

opaque type Or[A] = A
object Or extends OrInstances:
def apply[A](instance: A): Or[A] = instance
extension [A](or: Or[A]) def unify: A = or
extension [I[f[_], t] <: K0.Instances[f, t], F[_], T](inst: I[Or0[F], T])
@targetName("unifyK0") def unify: I[F, T] = inst
extension [I[f[_[_]], t[_]] <: K1.Instances[f, t], F[_[_]], T[_]](inst: I[Or1[F], T])
@targetName("unifyK1") def unify: I[F, T] = inst
extension [I[f[_[_[_]]], t[_[_]]] <: K11.Instances[f, t], F[_[_[_]]], T[_[_]]](inst: I[Or11[F], T])
@targetName("unifyK11") def unify: I[F, T] = inst
extension [I[f[_[_, _]], t[_, _]] <: K2.Instances[f, t], F[_[_, _]], T[_, _]](inst: I[Or2[F], T])
@targetName("unifyK2") def unify: I[F, T] = inst

sealed abstract class OrInstances:
inline given [A]: Derived.Or[A] = summonFrom {
case instance: A => Derived.Or(instance)
case derived: Derived[A] => Derived.Or(derived.instance)
}
39 changes: 39 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedApplicative.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cats.derived

import shapeless3.deriving.{Const, K1}
import cats.{Applicative, Monoid}

import scala.compiletime.*
import shapeless3.deriving.{Continue, K0, Labelling}

import scala.annotation.implicitNotFound
import scala.deriving.Mirror

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

inline def apply[F[_]]: Applicative[F] =
import DerivedApplicative.given
summonInline[DerivedApplicative[F]].instance

given [T](using T: Monoid[T]): DerivedApplicative[Const[T]] = new Applicative[Const[T]]:
def pure[A](x: A): Const[T][A] = T.empty
def ap[A, B](ff: T)(fa: T): Const[T][B] = T.combine(ff, fa)

given [F[_], G[_]](using F: Or[F], G: Or[G]): DerivedApplicative[[x] =>> F[G[x]]] =
F.unify.compose(G.unify)

given [F[_]](using inst: => K1.ProductInstances[Or, F]): DerivedApplicative[F] =
given K1.ProductInstances[Applicative, F] = inst.unify
new Product[Applicative, F] with DerivedApply.Product[Applicative, F] {}

trait Product[T[x[_]] <: Applicative[x], F[_]](using inst: K1.ProductInstances[T, F])
extends Applicative[F],
DerivedApply.Product[T, F]:
override def pure[A](x: A): F[A] = inst.construct([t[_]] => (apl: T[t]) => apl.pure[A](x))
Loading