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

Port invariant to new scheme #478

Merged
merged 6 commits into from
May 29, 2022
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
37 changes: 37 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedInvariant.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cats.derived

import cats.{Contravariant, Functor, Invariant}
import shapeless3.deriving.{Const, K1}

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

@implicitNotFound("""Could not derive an instance of Invariant[F] where F = ${F}.
Make sure that F[_] satisfies one of the following conditions:
* it is a constant type [x] => T
* it is a nested type [x] => G[H[x]] where G: Invariant and H: Invariant
* it is a generic case class where all fields have an Invariant instance
* it is a generic sealed trait where all subclasses have an Invariant instance""")
type DerivedInvariant[F[_]] = Derived[Invariant[F]]
object DerivedInvariant:
type Or[F[_]] = Derived.Or[Invariant[F]]
inline def apply[F[_]]: Invariant[F] =
import DerivedInvariant.given
summonInline[DerivedInvariant[F]].instance

given [T]: DerivedInvariant[Const[T]] = new Invariant[Const[T]]:
def imap[A, B](fa: T)(f: A => B)(g: B => A): T = fa

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

given [F[_]](using inst: => K1.Instances[Or, F]): DerivedInvariant[F] =
given K1.Instances[Invariant, F] = inst.unify
new Generic[Invariant, F] {}

trait Generic[T[x[_]] <: Invariant[x], F[_]](using inst: K1.Instances[T, F]) extends Invariant[F]:
def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] = inst.map(fa)(
[t[_]] => (inv: T[t], t0: t[A]) => inv.imap(t0)(f)(g)
)
14 changes: 0 additions & 14 deletions core/src/main/scala-3/cats/derived/invariant.scala

This file was deleted.

7 changes: 6 additions & 1 deletion core/src/main/scala-3/cats/derived/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ extension (x: NonEmptyTraverse.type) inline def derived[F[_]]: NonEmptyTraverse[
extension (x: SemigroupK.type) inline def derived[F[_]]: SemigroupK[F] = DerivedSemigroupK[F]
extension (x: MonoidK.type) inline def derived[F[_]]: MonoidK[F] = DerivedMonoidK[F]
extension (x: Contravariant.type) inline def derived[F[_]]: Contravariant[F] = DerivedContravariant[F]
extension (x: Invariant.type) inline def derived[F[_]]: Invariant[F] = DerivedInvariant[F]

object semiauto extends InvariantDerivation, PartialOrderDerivation, Instances:
object semiauto extends PartialOrderDerivation, Instances:

inline def eq[A]: Eq[A] = DerivedEq[A]
inline def hash[A]: Hash[A] = DerivedHash[A]
Expand All @@ -51,6 +52,7 @@ object semiauto extends InvariantDerivation, PartialOrderDerivation, Instances:
inline def semigroupK[F[_]]: SemigroupK[F] = DerivedSemigroupK[F]
inline def monoidK[F[_]]: MonoidK[F] = DerivedMonoidK[F]
inline def contravariant[F[_]]: Contravariant[F] = DerivedContravariant[F]
inline def invariant[F[_]]: Invariant[F] = DerivedInvariant[F]

object auto:
object eq:
Expand Down Expand Up @@ -115,3 +117,6 @@ object auto:

object contravariant:
inline given [F[_]](using NotGiven[Contravariant[F]]): Contravariant[F] = DerivedContravariant[F]

object invariant:
inline given [F[_]](using NotGiven[Invariant[F]]): Invariant[F] = DerivedInvariant[F]
101 changes: 101 additions & 0 deletions core/src/test/scala-3/cats/derived/InvariantSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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
package derived
import cats.laws.discipline.*
import cats.laws.discipline.arbitrary.*
import cats.laws.discipline.eq.*
import cats.laws.discipline.*

import scala.compiletime.*

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

inline def invariantTests[F[_]]: InvariantTests[F] = InvariantTests[F](summonInline)

inline def testInvariant(context: String): Unit = {
checkAll(s"$context.Invariant[TreeF]", invariantTests[TreeF].invariant[MiniInt, String, Boolean])
checkAll(s"$context.Invariant[GenAdtF]", invariantTests[GenericAdtF].invariant[MiniInt, String, Boolean])
// TODO https://github.com/typelevel/kittens/issues/473
// checkAll(s"$context.Invariant[InterleavedF]", invariantTests[InterleavedF].invariant[MiniInt, String, Boolean])
checkAll(s"$context.Invariant[AndCharF]", invariantTests[AndCharF].invariant[MiniInt, String, Boolean])
checkAll(s"$context.Invariant[ListSnoc", invariantTests[ListSnoc].invariant[MiniInt, String, Boolean])
checkAll(s"$context.Invariant[Bivariant]", invariantTests[Bivariant].invariant[MiniInt, String, Boolean])

checkAll(s"$context.Invariant is Serializable", SerializableTests.serializable(summonInline[Invariant[TreeF]]))

// TODO https://github.com/typelevel/kittens/issues/476
// test(s"$context.Invariant.imap is stack safe") {
// val I = summonInline[Invariant[ListSnoc]]
// val J = summonInline[Invariant[IList]]
// val n = 10000
// val largeIList = IList.fromSeq(1 until n)
// val largeSnoc = Snoc.fromSeq(1 until n) :: Nil
// val actualIList = IList.toList(J.imap(largeIList)(_ + 1)(_ - 1))
// val actualSnoc = I.imap(largeSnoc)(_ + 1)(_ - 1).flatMap(Snoc.toList)
// val expected = (2 until n + 1).toList
// assert(actualIList == expected)
// assert(actualSnoc == expected)
// }
}

locally {
import auto.invariant.given
testInvariant("auto")
}

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

object InvariantSuite:
import TestDefns.*

type ListSnoc[A] = List[Snoc[A]]
type GenericAdtF[A] = GenericAdt[A => Boolean]
type ListFToInt[A] = List[Snoc[A => Int]]
type InterleavedF[A] = Interleaved[A => Boolean]
type AndCharF[A] = (A => Boolean, Char)
type TreeF[A] = Tree[A => Boolean]

object semiInstances:
implicit val gadt: Invariant[GenericAdtF] = semiauto.invariant[GenericAdtF]
implicit val listSnocendo: Invariant[ListFToInt] = semiauto.invariant[ListFToInt]
// implicit val interleaveF: Invariant[InterleavedF] = semiauto.invariant[InterleavedF]
implicit val andCharF: Invariant[AndCharF] = semiauto.invariant[AndCharF]
implicit val treeF: Invariant[TreeF] = semiauto.invariant[TreeF]
implicit val pred: Invariant[Pred] = semiauto.invariant[Pred]
implicit val snoc: Invariant[ListSnoc] = semiauto.invariant[ListSnoc]
implicit val bivariant: Invariant[Bivariant] = semiauto.invariant[Bivariant]
implicit val ilist: Invariant[IList] = semiauto.invariant[IList]

case class Single[A](value: A) derives Invariant

enum Many[A] derives Invariant:
case Naught()
case More(value: A, rest: Many[A])

enum AtMostOne[A] derives Invariant:
case Naught()
case Single(value: A)

enum AtLeastOne[A] derives Invariant:
case Single(value: A)
case More(value: A, rest: Option[AtLeastOne[A]])
11 changes: 0 additions & 11 deletions core/src/test/scala-3/cats/derived/InvariantTests.scala

This file was deleted.