Skip to content

Commit

Permalink
Port EmptyK to dotty (#448)
Browse files Browse the repository at this point in the history
* Port EmptyK to dotty

* Fix formatting

* Support EmptyK for sum types

* Refactor EmptyKSuite

Co-authored-by: Georgi Krastev <joro.kr.21@gmail.com>
  • Loading branch information
andrzejressel and joroKr21 authored Apr 16, 2022
1 parent 735d569 commit 9c9123e
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 21 deletions.
5 changes: 4 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ lazy val commonSettings = Seq(
),
libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match {
case Some((3, _)) =>
Seq("org.typelevel" %%% "shapeless3-deriving" % shapeless3Version)
Seq(
"org.typelevel" %%% "shapeless3-deriving" % shapeless3Version,
"org.typelevel" %%% "shapeless3-test" % shapeless3Version % Test
)
case _ =>
Seq(
"com.chuusai" %%% "shapeless" % shapeless2Version,
Expand Down
44 changes: 44 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedEmptyK.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cats.derived

import alleycats.{Empty, EmptyK, Pure}
import cats.derived.util.Kinds
import shapeless3.deriving.{Const, K1}

import scala.annotation.implicitNotFound
import scala.compiletime.summonInline
import scala.util.NotGiven

@implicitNotFound("""Could not derive an instance of EmptyK[F] where F = ${F}.
Make sure that F[_] satisfies one of the following conditions:
* it is a constant type λ[x => T] where T: Empty
* it is a nested type λ[x => G[H[x]]] where G: EmptyK
* it is a nested type λ[x => G[H[x]]] where G: Pure and H: EmptyK
* it is a generic case class where all fields have an EmptyK instance
* it is a generic sealed trait where exactly one subtype has an EmptyK instance
Note: using kind-projector notation - https://github.com/typelevel/kind-projector""")
type DerivedEmptyK[F[_]] = Derived[EmptyK[F]]
object DerivedEmptyK:
type Or[F[_]] = Derived.Or[EmptyK[F]]
inline def apply[F[_]]: EmptyK[F] =
import DerivedEmptyK.given
summonInline[DerivedEmptyK[F]].instance

given [T](using T: Empty[T]): DerivedEmptyK[Const[T]] =
new EmptyK[Const[T]]:
def empty[A] = T.empty

given [F[_], G[_]](using F: Or[F]): DerivedEmptyK[[x] =>> F[G[x]]] =
new EmptyK[[x] =>> F[G[x]]]:
def empty[A] = F.unify.empty

given [F[_], G[_]](using NotGiven[Or[F]])(using F: Pure[F], G: Or[G]): DerivedEmptyK[[x] =>> F[G[x]]] =
new EmptyK[[x] =>> F[G[x]]]:
def empty[A] = F.pure(G.unify.empty)

given product[F[_]](using inst: K1.ProductInstances[Or, F]): DerivedEmptyK[F] =
new EmptyK[F]:
def empty[A]: F[A] = inst.unify.construct([f[_]] => (E: EmptyK[f]) => E.empty[A])

inline given coproduct[F[_]](using gen: K1.CoproductGeneric[F]): DerivedEmptyK[F] =
Kinds.summonOne1[Or, gen.MirroredElemTypes, F].unify
14 changes: 0 additions & 14 deletions core/src/main/scala-3/cats/derived/emptyk.scala

This file was deleted.

6 changes: 5 additions & 1 deletion 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: EmptyK.type) inline def derived[F[_]]: EmptyK[F] = DerivedEmptyK[F]
extension (x: Foldable.type) inline def derived[F[_]]: Foldable[F] = DerivedFoldable[F]
extension (x: Functor.type) inline def derived[F[_]]: Functor[F] = DerivedFunctor[F]
extension (x: Reducible.type) inline def derived[F[_]]: Reducible[F] = DerivedReducible[F]
Expand All @@ -25,7 +26,6 @@ extension (x: NonEmptyTraverse.type) inline def derived[F[_]]: NonEmptyTraverse[

object semiauto
extends ContravariantDerivation,
EmptyKDerivation,
InvariantDerivation,
MonoidKDerivation,
PartialOrderDerivation,
Expand All @@ -42,6 +42,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 emptyK[F[_]]: EmptyK[F] = DerivedEmptyK[F]
inline def foldable[F[_]]: Foldable[F] = DerivedFoldable[F]
inline def functor[F[_]]: Functor[F] = DerivedFunctor[F]
inline def reducible[F[_]]: Reducible[F] = DerivedReducible[F]
Expand Down Expand Up @@ -83,6 +84,9 @@ object auto:
object apply:
inline given [F[_]](using NotGiven[Apply[F]]): Apply[F] = DerivedApply[F]

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

object functor:
inline given [F[_]](using NotGiven[Functor[F]]): Functor[F] = DerivedFunctor[F]

Expand Down
12 changes: 9 additions & 3 deletions core/src/main/scala-3/cats/derived/util/Kinds.scala
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
package cats.derived.util

import shapeless3.deriving.K0.LiftP
import shapeless3.deriving._

import scala.compiletime.*
import scala.util.NotGiven

object Kinds:
inline def summonOne0[F[_], T, U]: F[U] =
summonOneFrom[LiftP[F, T]].asInstanceOf[F[U]]
summonOneFrom[K0.LiftP[F, T]].asInstanceOf[F[U]]

inline def summonOne1[F[_[_]], T[_], U[_]]: F[U] =
summonOneFrom[K1.LiftP[F, T]].asInstanceOf[F[U]]

inline def summonNone0[F[_], T]: Unit =
summonNoneFrom[LiftP[F, T]]
summonNoneFrom[K0.LiftP[F, T]]

inline def summonNone1[F[_[_]], T[_]]: Unit =
summonNoneFrom[K1.LiftP[F, T]]

transparent inline def summonOneFrom[T <: Tuple]: Any =
inline erasedValue[T] match
Expand Down
82 changes: 82 additions & 0 deletions core/src/test/scala-3/cats/derived/EmptyKSuite.scala
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.derived

import alleycats.{EmptyK, Pure}
import alleycats.std.all.*
import cats.data.NonEmptyList
import cats.laws.discipline.SerializableTests
import cats.syntax.all.*
import shapeless3.test.illTyped

import scala.compiletime.summonInline

class EmptyKSuite extends KittensSuite:
import EmptyKSuite.*
import TestDefns.*

given Pure[Box] with
def pure[A](a: A) = Box(a)

inline def emptyK[F[_]] =
summonInline[EmptyK[F]].empty

inline def testEmptyK(context: String): Unit =
test(s"$context.EmptyK[LOption]")(assert(emptyK[LOption] == Nil))
test(s"$context.EmptyK[PList]")(assert(emptyK[PList] == (Nil, Nil)))
test(s"$context.EmptyK[CaseClassWOption]")(assert(emptyK[CaseClassWOption] == CaseClassWOption(None)))
test(s"$context.EmptyK[NelOption]")(assert(emptyK[NelOption] == NonEmptyList.one(None)))
test(s"$context.EmptyK[IList]")(assert(emptyK[IList] == INil()))
test(s"$context.EmptyK[Snoc]")(assert(emptyK[Snoc] == SNil()))
test(s"$context.EmptyK respects existing instances")(assert(emptyK[BoxColor] == Box(Color(255, 255, 255))))
checkAll(s"$context.EmptyK is Serializable", SerializableTests.serializable(summonInline[EmptyK[LOption]]))

locally {
import auto.emptyK.given
testEmptyK("auto")
}

locally {
import semiInstances.given
testEmptyK("semiauto")
}

end EmptyKSuite

object EmptyKSuite:
import TestDefns.*

type LOption[A] = List[Option[A]]
type PList[A] = (List[A], List[A])
type NelOption[A] = NonEmptyList[Option[A]]
type BoxColor[A] = Box[Color[A]]

object semiInstances:
given EmptyK[LOption] = semiauto.emptyK
given EmptyK[PList] = semiauto.emptyK
given EmptyK[CaseClassWOption] = semiauto.emptyK
given EmptyK[NelOption] = semiauto.emptyK
given EmptyK[IList] = semiauto.emptyK
given EmptyK[Snoc] = semiauto.emptyK
given EmptyK[BoxColor] = semiauto.emptyK

final case class Color[A](r: Int, g: Int, b: Int)
object Color:
given EmptyK[Color] with
def empty[A] = Color(255, 255, 255)

end EmptyKSuite
3 changes: 1 addition & 2 deletions core/src/test/scala-3/cats/derived/EmptyKTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package cats.derived
import alleycats.*
import alleycats.std.all.*
import cats.*
import cats.derived.semiauto.*
import cats.derived.semiauto.given
import cats.derived.*

class EmptyKTests { //
case class Foo[A](i: String, l: List[A]) derives EmptyK
Expand Down

0 comments on commit 9c9123e

Please sign in to comment.