-
Notifications
You must be signed in to change notification settings - Fork 64
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Port invariant derivation to new scheme * Port scala 2 invariant tests to scala 3 * Consistent test naming * WIP extra Invariant instances * Tests for invariant derivation syntax
- Loading branch information
1 parent
1096075
commit 5de791e
Showing
5 changed files
with
144 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
) |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
core/src/test/scala-3/cats/derived/InvariantSuite.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]]) |
This file was deleted.
Oops, something went wrong.