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 NonEmptyTraverse derivation to Scala 3 #394

Merged
merged 1 commit into from Oct 11, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 9 additions & 12 deletions core/src/main/scala-3/cats/derived/DerivedFunctor.scala
@@ -1,9 +1,9 @@
package cats.derived

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

import scala.compiletime.*
import cats.Contravariant

type DerivedFunctor[F[_]] = Derived[Functor[F]]
object DerivedFunctor:
Expand All @@ -16,18 +16,15 @@ object DerivedFunctor:
def map[A, B](fa: T)(f: A => B): T = fa

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

given [F[_], G[_]](using F: Contravariant[F], G: Contravariant[G]): DerivedFunctor[[x] =>> F[G[x]]] =
F `compose` G
F.compose(G)

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

trait Generic[T[x[_]] <: Functor[x], F[_]](using inst: K1.Instances[T, F])
extends Functor[F]:
given K1.Instances[Functor, F] = inst.unify
new GenericFunctor[Functor, F] {}

def map[A, B](fa: F[A])(f: A => B): F[B] =
inst.map(fa: F[A]) { [f[_]] => (tf: T[f], fa: f[A]) =>
tf.map(fa)(f)
}
trait GenericFunctor[T[x[_]] <: Functor[x], F[_]](using inst: K1.Instances[T, F]) extends Functor[F]:
override def map[A, B](fa: F[A])(f: A => B): F[B] =
inst.map(fa: F[A])([f[_]] => (tf: T[f], fa: f[A]) => tf.map(fa)(f))
66 changes: 66 additions & 0 deletions core/src/main/scala-3/cats/derived/DerivedNonEmptyTraverse.scala
@@ -0,0 +1,66 @@
package cats.derived

import cats.{Applicative, Apply, NonEmptyTraverse, Traverse}
import shapeless3.deriving.{Const, K1}

import scala.compiletime.*

type DerivedNonEmptyTraverse[F[_]] = Derived[NonEmptyTraverse[F]]
object DerivedNonEmptyTraverse:
type Or[F[_]] = Derived.Or[NonEmptyTraverse[F]]
inline def apply[F[_]]: NonEmptyTraverse[F] =
import DerivedTraverse.given
import DerivedNonEmptyTraverse.given
summonInline[DerivedNonEmptyTraverse[F]].instance

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

def product[F[_]](ev: NonEmptyTraverse[?])(using
inst: K1.ProductInstances[DerivedTraverse.Or, F]
): DerivedNonEmptyTraverse[F] =
given K1.ProductInstances[Traverse, F] = inst.unify
new ProductNonEmptyTraverse[Traverse, F](ev) with DerivedReducible.Product[Traverse, F](ev) {}

inline given [F[_]](using gen: K1.ProductGeneric[F]): DerivedNonEmptyTraverse[F] =
product(K1.summonFirst[Or, gen.MirroredElemTypes, Const[Any]].unify)

given [F[_]](using inst: => K1.CoproductInstances[Or, F]): DerivedNonEmptyTraverse[F] =
given K1.CoproductInstances[NonEmptyTraverse, F] = inst.unify
new CoproductNonEmptyTraverse[NonEmptyTraverse, F] {}

trait ProductNonEmptyTraverse[T[x[_]] <: Traverse[x], F[_]](ev: NonEmptyTraverse[?])(using
inst: K1.ProductInstances[T, F]
) extends NonEmptyTraverse[F],
DerivedReducible.Product[T, F],
DerivedTraverse.ProductTraverse[T, F]:

// FIXME: Why is this override necessary?
override def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] =
super[ProductTraverse].traverse(fa)(f)

override def nonEmptyTraverse[G[_]: Apply, A, B](fa: F[A])(f: A => G[B]): G[F[B]] =
traverse[Alt[G], A, B](fa)(f.andThen(Left.apply)) match
case Left(value) => value
case Right(_) => ???

trait CoproductNonEmptyTraverse[T[x[_]] <: NonEmptyTraverse[x], F[_]](using
inst: K1.CoproductInstances[T, F]
) extends NonEmptyTraverse[F],
DerivedReducible.Coproduct[T, F],
DerivedTraverse.CoproductTraverse[T, F]:

override def nonEmptyTraverse[G[_]: Apply, A, B](fa: F[A])(f: A => G[B]): G[F[B]] =
inst.fold(fa)([f[_]] => (tf: T[f], fa: f[A]) => tf.nonEmptyTraverse(fa)(f).asInstanceOf[G[F[B]]])

private type Alt[F[_]] = [A] =>> Either[F[A], A]
private given [F[_]](using F: Apply[F]): Applicative[Alt[F]] with
override def pure[A](x: A) = Right(x)
override def map[A, B](fa: Alt[F][A])(f: A => B) = fa match
case Left(fa) => Left(F.map(fa)(f))
case Right(a) => Right(f(a))
override def ap[A, B](ff: Alt[F][A => B])(fa: Alt[F][A]) = (ff, fa) match
case (Left(ff), Left(fa)) => Left(F.ap(ff)(fa))
case (Left(ff), Right(a)) => Left(F.map(ff)(_(a)))
case (Right(f), Left(fa)) => Left(F.map(fa)(f))
case (Right(f), Right(a)) => Right(f(a))
64 changes: 28 additions & 36 deletions core/src/main/scala-3/cats/derived/DerivedTraverse.scala
@@ -1,7 +1,8 @@
package cats.derived

import cats.{Applicative, Eval, Traverse}
import shapeless3.deriving.{Const, Continue, K1}
import shapeless3.deriving.{Const, K1}

import scala.compiletime.*

type DerivedTraverse[F[_]] = Derived[Traverse[F]]
Expand All @@ -13,45 +14,36 @@ object DerivedTraverse:

given [T]: DerivedTraverse[Const[T]] = new Traverse[Const[T]]:
override def map[A, B](fa: T)(f: A => B): T = fa
def foldLeft[A, B](fa: T, b: B)(f: (B, A) => B): B = b
def foldRight[A, B](fa: T, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb
def traverse[G[_], A, B](fa: T)(f: A => G[B])(using G: Applicative[G]): G[T] = G.pure(fa)
override def foldLeft[A, B](fa: T, b: B)(f: (B, A) => B): B = b
override def foldRight[A, B](fa: T, lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb
override def traverse[G[_], A, B](fa: T)(f: A => G[B])(using G: Applicative[G]): G[T] = G.pure(fa)

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

given [F[_]](using inst: K1.ProductInstances[Or, F]): DerivedTraverse[F] =
given K1.ProductInstances[Traverse, F] = inst.unify
new Product[Traverse, F] {}
new ProductTraverse[Traverse, F] {}

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

trait Product[T[x[_]] <: Traverse[x], F[_]](using inst: K1.ProductInstances[T, F])
extends DerivedFunctor.Generic[T, F], DerivedFoldable.Product[T, F], Traverse[F]:

def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] =
inst.traverse[A, G, B](fa) { [a, b] => (ga: G[a], f: a => b) =>
G.map(ga)(f)
} { [a] => (x: a) =>
G.pure(x)
} { [a, b] => (gf: G[a => b], ga: G[a]) =>
G.ap(gf)(ga)
} { [f[_]] => (tf: T[f], fa: f[A]) =>
tf.traverse(fa)(f)
}

trait Coproduct[T[x[_]] <: Traverse[x], F[_]](using inst: K1.CoproductInstances[T, F])
extends DerivedFunctor.Generic[T, F], DerivedFoldable.Coproduct[T, F], Traverse[F]:

def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] =
inst.traverse[A, G, B](fa) { [a, b] => (ga: G[a], f: a => b) =>
G.map(ga)(f)
} { [a] => (x: a) =>
G.pure(x)
} { [a, b] => (gf: G[a => b], ga: G[a]) =>
G.ap(gf)(ga)
} { [f[_]] => (tf: T[f], fa: f[A]) =>
tf.traverse(fa)(f)
}
new CoproductTraverse[Traverse, F] {}

trait ProductTraverse[T[x[_]] <: Traverse[x], F[_]](using inst: K1.ProductInstances[T, F])
extends Traverse[F],
DerivedFunctor.GenericFunctor[T, F],
DerivedFoldable.Product[T, F]:

override def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(using G: Applicative[G]): G[F[B]] =
val pure = [a] => (x: a) => G.pure(x)
val map = [a, b] => (ga: G[a], f: a => b) => G.map(ga)(f)
val ap = [a, b] => (gf: G[a => b], ga: G[a]) => G.ap(gf)(ga)
inst.traverse[A, G, B](fa)(map)(pure)(ap)([f[_]] => (tf: T[f], fa: f[A]) => tf.traverse(fa)(f))

trait CoproductTraverse[T[x[_]] <: Traverse[x], F[_]](using inst: K1.CoproductInstances[T, F])
extends Traverse[F],
DerivedFunctor.GenericFunctor[T, F],
DerivedFoldable.Coproduct[T, F]:

override def traverse[G[_]: Applicative, A, B](fa: F[A])(f: A => G[B]): G[F[B]] =
inst.fold(fa)([f[_]] => (tf: T[f], fa: f[A]) => tf.traverse(fa)(f).asInstanceOf[G[F[B]]])
27 changes: 16 additions & 11 deletions core/src/main/scala-3/cats/derived/package.scala
Expand Up @@ -6,17 +6,18 @@ import cats.kernel.{CommutativeSemigroup, CommutativeMonoid}

import scala.util.NotGiven

extension (E: Eq.type) inline def derived[A]: Eq[A] = DerivedEq[A]
extension (H: Hash.type) inline def derived[A]: Hash[A] = DerivedHash[A]
extension (E: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty[A]
extension (S: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup[A]
extension (M: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid[A]
extension (S: CommutativeSemigroup.type) inline def derived[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A]
extension (S: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A]
extension (F: Foldable.type) inline def derived[F[_]]: Foldable[F] = DerivedFoldable[F]
extension (F: Functor.type) inline def derived[F[_]]: Functor[F] = DerivedFunctor[F]
extension (F: Reducible.type) inline def derived[F[_]]: Reducible[F] = DerivedReducible[F]
extension (F: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTraverse[F]
extension (x: Eq.type) inline def derived[A]: Eq[A] = DerivedEq[A]
extension (x: Hash.type) inline def derived[A]: Hash[A] = DerivedHash[A]
extension (x: Empty.type) inline def derived[A]: Empty[A] = DerivedEmpty[A]
extension (x: Semigroup.type) inline def derived[A]: Semigroup[A] = DerivedSemigroup[A]
extension (x: Monoid.type) inline def derived[A]: Monoid[A] = DerivedMonoid[A]
extension (x: CommutativeSemigroup.type) inline def derived[A]: CommutativeSemigroup[A] = DerivedCommutativeSemigroup[A]
extension (x: CommutativeMonoid.type) inline def derived[A]: CommutativeMonoid[A] = DerivedCommutativeMonoid[A]
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]
extension (x: Traverse.type) inline def derived[F[_]]: Traverse[F] = DerivedTraverse[F]
extension (x: NonEmptyTraverse.type) inline def derived[F[_]]: NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F]

object semiauto
extends ContravariantDerivation,
Expand All @@ -40,6 +41,7 @@ object semiauto
inline def functor[F[_]]: Functor[F] = DerivedFunctor[F]
inline def reducible[F[_]]: Reducible[F] = DerivedReducible[F]
inline def traverse[F[_]]: Traverse[F] = DerivedTraverse[F]
inline def nonEmptyTraverse[F[_]]: NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F]

object auto:
object eq:
Expand Down Expand Up @@ -74,3 +76,6 @@ object auto:

object traverse:
inline given [F[_]](using NotGiven[Traverse[F]]): Traverse[F] = DerivedTraverse[F]

object nonEmptyTraverse:
inline given [F[_]](using NotGiven[NonEmptyTraverse[F]]): NonEmptyTraverse[F] = DerivedNonEmptyTraverse[F]
109 changes: 109 additions & 0 deletions core/src/test/scala-3/cats/derived/NonEmptyTraverseSuite.scala
@@ -0,0 +1,109 @@
/*
* 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 cats.{Eq, NonEmptyTraverse}
import cats.data.{NonEmptyList, NonEmptyVector, OneAnd}
import cats.laws.discipline.arbitrary.*
import cats.laws.discipline.{NonEmptyTraverseTests, SerializableTests}
import cats.syntax.all.*
import org.scalacheck.Arbitrary

import scala.compiletime.*

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

inline def nonEmptyTraverseTests[F[_]]: NonEmptyTraverseTests[F] =
NonEmptyTraverseTests[F](summonInline)

inline def testReducible(inline context: String): Unit =
checkAll(
s"$context.NonEmptyTraverse[ICons]",
nonEmptyTraverseTests[ICons].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]
)

checkAll(
s"$context.NonEmptyTraverse[Tree]",
nonEmptyTraverseTests[Tree].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]
)

checkAll(
s"$context.NonEmptyTraverse[NelSCons]",
nonEmptyTraverseTests[NelSCons].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]
)

checkAll(
s"$context.NonEmptyTraverse[NelAndOne]",
nonEmptyTraverseTests[NelAndOne].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]
)

checkAll(
s"$context.NonEmptyTraverse[ListAndNel]",
nonEmptyTraverseTests[ListAndNel].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]
)

checkAll(
s"$context.NonEmptyTraverse[Interleaved]",
nonEmptyTraverseTests[Interleaved].nonEmptyTraverse[Option, Int, Int, Int, Int, Option, Option]
)

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

locally {
import auto.nonEmptyTraverse.given
testReducible("auto")
}

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

end NonEmptyTraverseSuite

object NonEmptyTraverseSuite:
import TestDefns.*

type NelSCons[A] = NonEmptyList[SCons[A]]
type NelAndOne[A] = NonEmptyList[OneAnd[List, A]]

// FIXME: Doesn't work if we define `ListAndNel` as a type alias
final case class ListAndNel[A](list: List[A], nel: NonEmptyList[A])
object ListAndNel:
given [A: Eq]: Eq[ListAndNel[A]] =
(x, y) => x.list === y.list && x.nel === y.nel

given [A: Arbitrary]: Arbitrary[ListAndNel[A]] =
Arbitrary(for
list <- Arbitrary.arbitrary[List[A]]
nel <- Arbitrary.arbitrary[NonEmptyList[A]]
yield ListAndNel(list, nel))

object semiInstances:
given NonEmptyTraverse[ICons] = semiauto.nonEmptyTraverse
given NonEmptyTraverse[Tree] = semiauto.nonEmptyTraverse
given NonEmptyTraverse[NelSCons] = semiauto.nonEmptyTraverse
given NonEmptyTraverse[NelAndOne] = semiauto.nonEmptyTraverse
given NonEmptyTraverse[ListAndNel] = semiauto.nonEmptyTraverse
given NonEmptyTraverse[Interleaved] = semiauto.nonEmptyTraverse

end NonEmptyTraverseSuite