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 2] Add Record and Union instances #517

Merged
merged 1 commit into from
Dec 17, 2023
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
13 changes: 12 additions & 1 deletion core/src/main/scala-2/cats/derived/applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
package cats
package derived

import cats.derived.util.VersionSpecific.OrElse
import shapeless._
import util.VersionSpecific.OrElse
import shapeless.labelled._

import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -49,6 +50,16 @@ abstract private[derived] class MkApplicativeDerivation extends MkApplicativeNes
def ap[A, B](ff: T)(fa: T) = T.combine(ff, fa)
override def map[A, B](fa: T)(f: A => B) = fa
}

implicit def mkApplicativeFieldType[K, V[_]](implicit
V: Strict[ApplicativeOrMk[V]]
): MkApplicative[λ[a => FieldType[K, V[a]]]] =
new MkApplicative[λ[a => FieldType[K, V[a]]]] {
private val v = V.value.unify
def pure[A](x: A) = field[K][V[A]](v.pure(x))
def ap[A, B](ff: FieldType[K, V[A => B]])(fa: FieldType[K, V[A]]) = field[K][V[B]](v.ap(ff)(fa))
override def map[A, B](fa: FieldType[K, V[A]])(f: A => B) = field[K][V[B]](v.map(fa)(f))
}
}

abstract private[derived] class MkApplicativeNested extends MkApplicativeGeneric {
Expand Down
10 changes: 9 additions & 1 deletion core/src/main/scala-2/cats/derived/apply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
package cats
package derived

import cats.derived.util.VersionSpecific.OrElse
import shapeless._
import util.VersionSpecific.OrElse
import shapeless.labelled._

import scala.annotation.implicitNotFound

Expand Down Expand Up @@ -47,6 +48,13 @@ abstract private[derived] class MkApplyDerivation extends MkApplyNested {
def ap[A, B](ff: T)(fa: T) = T.combine(ff, fa)
def map[A, B](fa: T)(f: A => B) = fa
}

implicit def mkApplyFieldType[K, V[_]](implicit V: Strict[ApplyOrMk[V]]): MkApply[λ[a => FieldType[K, V[a]]]] =
new MkApply[λ[a => FieldType[K, V[a]]]] {
private val v = V.value.unify
def ap[A, B](ff: FieldType[K, V[A => B]])(fa: FieldType[K, V[A]]) = field[K][V[B]](v.ap(ff)(fa))
def map[A, B](fa: FieldType[K, V[A]])(f: A => B) = field[K][V[B]](v.map(fa)(f))
}
}

abstract private[derived] class MkApplyNested extends MkApplyGeneric {
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/scala-2/cats/derived/functor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package cats
package derived

import shapeless._
import shapeless.labelled._
import util.VersionSpecific.OrElse

import scala.annotation.implicitNotFound
Expand Down Expand Up @@ -50,6 +51,13 @@ abstract private[derived] class MkFunctorDerivation extends MkFunctorNested {
new MkFunctor[Const[T]#λ] {
def safeMap[A, B](t: T)(f: A => Eval[B]) = Eval.now(t)
}

implicit def mkFunctorFieldType[K, V[_]](implicit V: Strict[FunctorOrMk[V]]): MkFunctor[λ[a => FieldType[K, V[a]]]] =
new MkFunctor[λ[a => FieldType[K, V[a]]]] {
private val v = V.value
def safeMap[A, B](fa: FieldType[K, V[A]])(f: A => Eval[B]) =
mkSafeMap(v)(fa)(f).map(field[K].apply)
}
}

abstract private[derived] class MkFunctorNested extends MkFunctorNestedContra {
Expand Down
48 changes: 48 additions & 0 deletions core/src/test/scala-2/cats/derived/KittensSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@

package cats.derived

import cats.Eq
import cats.platform.Platform
import cats.syntax.AllSyntax
import munit.DisciplineSuite
import org.scalacheck.Test.Parameters
import org.scalacheck.{Arbitrary, Cogen, Gen}
import shapeless._
import shapeless.labelled._

/** An opinionated stack of traits to improve consistency and reduce boilerplate in Kittens tests. Note that unlike the
* corresponding CatsSuite in the Cat project, this trait does not mix in any instances.
Expand All @@ -31,4 +35,48 @@ abstract class KittensSuite extends DisciplineSuite with AllSyntax {
.withWorkers(if (Platform.isJvm) 2 else 1)
.withMaxSize(if (Platform.isJvm) 10 else 5)
.withMinSize(0)

implicit def fieldTypeArb[K, V: Arbitrary]: Arbitrary[FieldType[K, V]] =
Arbitrary(Arbitrary.arbitrary[V].map(field[K].apply))

implicit def fieldTypeCogen[K, V: Cogen]: Cogen[FieldType[K, V]] =
Cogen[V].contramap(identity)

implicit def fieldTypeEq[K, V: Eq]: Eq[FieldType[K, V]] =
Eq.by(identity[V])

implicit val hNilArb: Arbitrary[HNil] = Arbitrary(Gen.const(HNil))
implicit val hNilCogen: Cogen[HNil] = Cogen[Unit].contramap(_ => ())
implicit val hNilEq: Eq[HNil] = Eq.allEqual

implicit val cNilArb: Arbitrary[CNil] = Arbitrary(Gen.fail)
implicit val cNilCogen: Cogen[CNil] = Cogen[Unit].contramap(_ => ())
implicit val cNilEq: Eq[CNil] = Eq.allEqual

implicit def hConsArb[H: Arbitrary, T <: HList: Arbitrary]: Arbitrary[H :: T] =
Arbitrary(Arbitrary.arbitrary[(H, T)].map { case (h, t) => h :: t })

implicit def hConsCogen[H: Cogen, T <: HList: Cogen]: Cogen[H :: T] =
Cogen[(H, T)].contramap { case h :: t => (h, t) }

implicit def hConsEq[H: Eq, T <: HList: Eq]: Eq[H :: T] =
Eq.by { case h :: t => (h, t) }

implicit def cConsArb[L: Arbitrary, R <: Coproduct: Arbitrary]: Arbitrary[L :+: R] =
Arbitrary(Arbitrary.arbitrary[Either[L, R]].map {
case Left(l) => Inl(l)
case Right(r) => Inr(r)
})

implicit def cConsCogen[L: Cogen, R <: Coproduct: Cogen]: Cogen[L :+: R] =
Cogen[Either[L, R]].contramap {
case Inl(l) => Left(l)
case Inr(r) => Right(r)
}

implicit def cConsEq[L: Eq, R <: Coproduct: Eq]: Eq[L :+: R] =
Eq.by {
case Inl(l) => Left(l)
case Inr(r) => Right(r)
}
}
11 changes: 11 additions & 0 deletions core/src/test/scala-2/cats/derived/adtdefns.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,21 @@ import cats.{Eq, Eval}
import org.scalacheck.rng.Seed
import org.scalacheck.{Arbitrary, Cogen, Gen}
import shapeless.Witness
import shapeless.labelled.FieldType

import scala.annotation.tailrec

object TestDefns {
type ->>[K, V] = FieldType[K, V]

sealed trait WeekDay
sealed trait Mon extends WeekDay
sealed trait Tue extends WeekDay
sealed trait Wed extends WeekDay
sealed trait Thu extends WeekDay
sealed trait Fri extends WeekDay
sealed trait Sat extends WeekDay
sealed trait Sun extends WeekDay

sealed trait Rgb
case object Red extends Rgb
Expand Down
11 changes: 9 additions & 2 deletions core/src/test/scala-2/cats/derived/applicative.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package cats
package derived
import cats.laws.discipline.{ApplicativeTests, SerializableTests}

import cats.laws.discipline.SemigroupalTests.Isomorphisms
import cats.laws.discipline.{ApplicativeTests, SerializableTests}
import shapeless._

class ApplicativeSuite extends KittensSuite {
import ApplicativeSuite._
Expand All @@ -29,11 +31,13 @@ class ApplicativeSuite extends KittensSuite {
optList: Applicative[OptList],
andInt: Applicative[AndInt],
interleaved: Applicative[Interleaved],
listBox: Applicative[ListBox]
listBox: Applicative[ListBox],
record: Applicative[Record]
): Unit = {
implicit val isoOptList: Isomorphisms[OptList] = Isomorphisms.invariant(optList)
implicit val isoAndInt: Isomorphisms[AndInt] = Isomorphisms.invariant(andInt)
implicit val isoListBox: Isomorphisms[ListBox] = Isomorphisms.invariant(listBox)
implicit val isoRecord: Isomorphisms[Record] = Isomorphisms.invariant(record)
checkAll(
s"$context.Applicative[CaseClassWOption]",
ApplicativeTests[CaseClassWOption].applicative[Int, String, Long]
Expand All @@ -42,6 +46,7 @@ class ApplicativeSuite extends KittensSuite {
checkAll(s"$context.Applicative[AndInt]", ApplicativeTests[AndInt].applicative[Int, String, Long])
checkAll(s"$context.Applicative[Interleaved]", ApplicativeTests[Interleaved].applicative[Int, String, Long])
checkAll(s"$context.Applicative[ListBox]", ApplicativeTests[ListBox].applicative[Int, String, Long])
checkAll(s"$context.Applicative[Record]", ApplicativeTests[Record].applicative[Int, String, Long])
checkAll(s"$context.Applicative is Serializable", SerializableTests.serializable(Applicative[Interleaved]))
}

Expand All @@ -67,12 +72,14 @@ object ApplicativeSuite {
type OptList[A] = Option[List[A]]
type AndInt[A] = (A, Int)
type ListBox[A] = List[Box[A]]
type Record[A] = (Mon ->> Option[A]) :: (Sun ->> List[A]) :: HNil

object semiInstances {
implicit val caseClassWOption: Applicative[CaseClassWOption] = semiauto.applicative
implicit val optList: Applicative[OptList] = semiauto.applicative
implicit val andInt: Applicative[AndInt] = semiauto.applicative
implicit val interleaved: Applicative[Interleaved] = semiauto.applicative
implicit val listBox: Applicative[ListBox] = semiauto.applicative
implicit val record: Applicative[Record] = semiauto.applicative
}
}
11 changes: 9 additions & 2 deletions core/src/test/scala-2/cats/derived/apply.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package cats
package derived
import cats.laws.discipline.{ApplyTests, SerializableTests}

import cats.laws.discipline.SemigroupalTests.Isomorphisms
import cats.laws.discipline.{ApplyTests, SerializableTests}
import shapeless._

class ApplySuite extends KittensSuite {
import ApplySuite._
Expand All @@ -29,16 +31,19 @@ class ApplySuite extends KittensSuite {
optList: Apply[OptList],
andInt: Apply[AndInt],
interleaved: Apply[Interleaved],
listBox: Apply[ListBox]
listBox: Apply[ListBox],
record: Apply[Record]
): Unit = {
implicit val isoOptList: Isomorphisms[OptList] = Isomorphisms.invariant(optList)
implicit val isoAndInt: Isomorphisms[AndInt] = Isomorphisms.invariant(andInt)
implicit val isoListBox: Isomorphisms[ListBox] = Isomorphisms.invariant(listBox)
implicit val isoRecord: Isomorphisms[Record] = Isomorphisms.invariant(record)
checkAll(s"$context.Apply[CaseClassWOption]", ApplyTests[CaseClassWOption].apply[Int, String, Long])
checkAll(s"$context.Apply[OptList]", ApplyTests[OptList].apply[Int, String, Long])
checkAll(s"$context.Apply[AndInt]", ApplyTests[AndInt].apply[Int, String, Long])
checkAll(s"$context.Apply[Interleaved]", ApplyTests[Interleaved].apply[Int, String, Long])
checkAll(s"$context.Apply[ListBox]", ApplyTests[ListBox].apply[Int, String, Long])
checkAll(s"$context.Apply[Record]", ApplyTests[Record].apply[Int, String, Long])
checkAll(s"$context.Apply is Serializable", SerializableTests.serializable(Apply[Interleaved]))
}

Expand All @@ -64,12 +69,14 @@ object ApplySuite {
type OptList[A] = Option[List[A]]
type AndInt[A] = (A, Int)
type ListBox[A] = List[Box[A]]
type Record[A] = (Mon ->> Option[A]) :: (Sun ->> List[A]) :: HNil

object semiInstances {
implicit val caseClassWOption: Apply[CaseClassWOption] = semiauto.apply
implicit val optList: Apply[OptList] = semiauto.apply
implicit val andInt: Apply[AndInt] = semiauto.apply
implicit val interleaved: Apply[Interleaved] = semiauto.apply
implicit val listBox: Apply[ListBox] = semiauto.apply
implicit val record: Apply[Record] = semiauto.apply
}
}
12 changes: 11 additions & 1 deletion core/src/test/scala-2/cats/derived/functor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@

package cats
package derived

import cats.laws.discipline._
import cats.laws.discipline.eq._
import shapeless._

class FunctorSuite extends KittensSuite {
import FunctorSuite._
Expand All @@ -36,7 +38,9 @@ class FunctorSuite extends KittensSuite {
andChar: Functor[AndChar],
interleaved: Functor[Interleaved],
nestedPred: Functor[NestedPred],
singletons: Functor[Singletons]
singletons: Functor[Singletons],
record: Functor[Record],
union: Functor[Union]
): Unit = {
checkAll(s"$context.Functor[IList]", FunctorTests[IList].functor[Int, String, Long])
checkAll(s"$context.Functor[Tree]", FunctorTests[Tree].functor[Int, String, Long])
Expand All @@ -47,6 +51,8 @@ class FunctorSuite extends KittensSuite {
checkAll(s"$context.Functor[Interleaved]", FunctorTests[Interleaved].functor[Int, String, Long])
checkAll(s"$context.Functor[NestedPred]", FunctorTests[NestedPred].functor[Boolean, Int, Boolean])
checkAll(s"$context.Functor[Singletons]", FunctorTests[Singletons].functor[Boolean, Int, Boolean])
checkAll(s"$context.Functor[Record]", FunctorTests[Record].functor[Boolean, Int, Boolean])
checkAll(s"$context.Functor[Union]", FunctorTests[Union].functor[Boolean, Int, Boolean])
checkAll(s"$context.Functor is Serializable", SerializableTests.serializable(Functor[Tree]))

test(s"$context.Functor.map is stack safe") {
Expand Down Expand Up @@ -85,6 +91,8 @@ object FunctorSuite {
type AndChar[A] = (A, Char)
type Predicate[A] = A => Boolean
type NestedPred[A] = Predicate[Predicate[A]]
type Record[A] = (Mon ->> Option[A]) :: (Sun ->> List[A]) :: HNil
type Union[A] = (Tue ->> IList[A]) :+: (Wed ->> Tree[A]) :+: CNil

object semiInstances {
implicit val iList: Functor[IList] = semiauto.functor
Expand All @@ -96,5 +104,7 @@ object FunctorSuite {
implicit val interleaved: Functor[Interleaved] = semiauto.functor
implicit val nestedPred: Functor[NestedPred] = semiauto.functor
implicit val singletons: Functor[Singletons] = semiauto.functor
implicit val record: Functor[Record] = semiauto.functor
implicit val union: Functor[Union] = semiauto.functor
}
}