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

Add Defer typeclass, laws and implementations #2279

Merged
merged 9 commits into from
Jul 22, 2018
33 changes: 33 additions & 0 deletions core/src/main/scala/cats/Defer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package cats

/**
* Defer is a type class that shows the ability to defer creation
* inside of the type constructor F[_].
*
* This comes up with F[_] types that are implemented with a trampoline
* or are based on function application.
*
* The law is that defer(fa) is equivalent to fa, but not evaluated immediately,
* so
* {{{
* import cats._
* import cats.implicits._
*
* var evaluated = false
* val dfa =
* Defer[Eval].defer {
* evaluated = true
* Eval.now(21)
* }
*
* assert(!evaluated)
* Eq[Eval[Int]].eqv(dfa, Eval.now(21))
* }}}
*/
trait Defer[F[_]] extends Serializable {
def defer[A](fa: => F[A]): F[A]
}

object Defer {
def apply[F[_]](implicit defer: Defer[F]): Defer[F] = defer
}
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/Eval.scala
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,12 @@ private[cats] sealed abstract class EvalInstances extends EvalInstances0 {
def coflatMap[A, B](fa: Eval[A])(f: Eval[A] => B): Eval[B] = Later(f(fa))
}

implicit val catsDeferForEval: Defer[Eval] =
new Defer[Eval] {
def defer[A](e: => Eval[A]): Eval[A] =
Eval.defer(e)
}

implicit val catsReducibleForEval: Reducible[Eval] =
new Reducible[Eval] {
def foldLeft[A, B](fa: Eval[A], b: B)(f: (B, A) => B): B =
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/EitherT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,11 @@ private[data] abstract class EitherTInstances extends EitherTInstances1 {
implicit def catsMonoidForEitherT[F[_], L, A](implicit F: Monoid[F[Either[L, A]]]): Monoid[EitherT[F, L, A]] =
new EitherTMonoid[F, L, A] { implicit val F0 = F }

implicit def catsDataDeferForEitherT[F[_], L](implicit F: Defer[F]): Defer[EitherT[F, L, ?]] =
new Defer[EitherT[F, L, ?]] {
def defer[A](fa: => EitherT[F, L, A]): EitherT[F, L, A] =
EitherT(F.defer(fa.value))
}
}

private[data] abstract class EitherTInstances1 extends EitherTInstances2 {
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/IndexedReaderWriterStateT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,12 @@ private[data] sealed abstract class IRWSTInstances extends IRWSTInstances1 {
implicit def L: Monoid[L] = L0
}

implicit def catsDataDeferForIRWST[F[_], E, L, SA, SB](implicit F: Defer[F]): Defer[IndexedReaderWriterStateT[F, E, L, SA, SB, ?]] =
new Defer[IndexedReaderWriterStateT[F, E, L, SA, SB, ?]] {
def defer[A](fa: => IndexedReaderWriterStateT[F, E, L, SA, SB, A]): IndexedReaderWriterStateT[F, E, L, SA, SB, A] =
IndexedReaderWriterStateT.applyF(F.defer(fa.runF))
}

}

private[data] sealed abstract class IRWSTInstances1 extends IRWSTInstances2 {
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/IndexedStateT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,12 @@ private[data] sealed abstract class IndexedStateTInstances extends IndexedStateT
implicit def catsDataAlternativeForIndexedStateT[F[_], S](implicit FM: Monad[F],
FA: Alternative[F]): Alternative[IndexedStateT[F, S, S, ?]] with Monad[IndexedStateT[F, S, S, ?]] =
new IndexedStateTAlternative[F, S] { implicit def F = FM; implicit def G = FA }

implicit def catsDataDeferForIndexedStateT[F[_], SA, SB](implicit F: Defer[F]): Defer[IndexedStateT[F, SA, SB, ?]] =
new Defer[IndexedStateT[F, SA, SB, ?]] {
def defer[A](fa: => IndexedStateT[F, SA, SB, A]): IndexedStateT[F, SA, SB, A] =
IndexedStateT.applyF(F.defer(fa.runF))
}
}

private[data] sealed abstract class IndexedStateTInstances1 extends IndexedStateTInstances2 {
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/IorT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,12 @@ private[data] abstract class IorTInstances extends IorTInstances1 {
Monad[IorT[M, E, ?]]
}
}

implicit def catsDataDeferForIor[F[_], E](implicit F: Defer[F]): Defer[IorT[F, E, ?]] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing T after catsDataDeferForIor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like we are still missing the T.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new Defer[IorT[F, E, ?]] {
def defer[A](fa: => IorT[F, E, A]): IorT[F, E, A] =
IorT(F.defer(fa.value))
}
}

private[data] abstract class IorTInstances1 extends IorTInstances2 {
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ private[data] sealed abstract class KleisliInstances extends KleisliInstances0 {
new KleisliArrowChoice[F] {
def F: Monad[F] = M
}

implicit def catsDataDeferForKleisli[F[_], A](implicit F: Defer[F]): Defer[Kleisli[F, A, ?]] =
new Defer[Kleisli[F, A, ?]] {
def defer[B](fa: => Kleisli[F, A, B]): Kleisli[F, A, B] = {
lazy val cacheFa = fa
Kleisli[F, A, B] { a => F.defer(cacheFa.run(a)) }
}
}
}

private[data] sealed abstract class KleisliInstances0 extends KleisliInstances1 {
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/Nested.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ private[data] sealed abstract class NestedInstances extends NestedInstances0 {
new NestedContravariantMonoidal[F, G] with NestedContravariant[F, G] {
val FG: ContravariantMonoidal[λ[α => F[G[α]]]] = Applicative[F].composeContravariantMonoidal[G]
}

implicit def catsDataDeferForNested[F[_], G[_]](implicit F: Defer[F]): Defer[Nested[F, G, ?]] =
new Defer[Nested[F, G, ?]] {
def defer[A](fa: => Nested[F, G, A]): Nested[F, G, A] =
Nested(F.defer(fa.value))
}
}

private[data] sealed abstract class NestedInstances0 extends NestedInstances1 {
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,12 @@ private[data] sealed abstract class OptionTInstances extends OptionTInstances0 {

implicit def catsDataShowForOptionT[F[_], A](implicit F: Show[F[Option[A]]]): Show[OptionT[F, A]] =
Contravariant[Show].contramap(F)(_.value)

implicit def catsDataDeferForOptionT[F[_]](implicit F: Defer[F]): Defer[OptionT[F, ?]] =
new Defer[OptionT[F, ?]] {
def defer[A](fa: => OptionT[F, A]): OptionT[F, A] =
OptionT(F.defer(fa.value))
}
}

private[data] sealed abstract class OptionTInstances0 extends OptionTInstances1 {
Expand Down
10 changes: 10 additions & 0 deletions core/src/main/scala/cats/data/Tuple2K.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ private[data] sealed abstract class Tuple2KInstances extends Tuple2KInstances0 {
def F: ContravariantMonoidal[F] = FD
def G: ContravariantMonoidal[G] = GD
}

implicit def catsDataDeferForTuple2K[F[_], G[_]](implicit F: Defer[F], G: Defer[G]): Defer[Tuple2K[F, G, ?]] =
new Defer[Tuple2K[F, G, ?]] {
def defer[A](fa: => Tuple2K[F, G, A]): Tuple2K[F, G, A] = {
// Make sure we only evaluate once on both the first and second
lazy val cacheFa = fa

Tuple2K(F.defer(cacheFa.first), G.defer(cacheFa.second))
}
}
}

private[data] sealed abstract class Tuple2KInstances0 extends Tuple2KInstances1 {
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/WriterT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 {

implicit def catsDataTraverseForWriterTId[L](implicit F: Traverse[Id]): Traverse[WriterT[Id, L, ?]] =
catsDataTraverseForWriterT[Id, L](F)

implicit def catsDataDeferForWriterT[F[_], L](implicit F: Defer[F]): Defer[WriterT[F, L, ?]] =
new Defer[WriterT[F, L, ?]] {
def defer[A](fa: => WriterT[F, L, A]): WriterT[F, L, A] =
WriterT(F.defer(fa.run))
}
}

private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 {
Expand Down
6 changes: 6 additions & 0 deletions free/src/main/scala/cats/free/Free.scala
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,12 @@ object Free extends FreeInstances {
override def map[A, B](fa: Free[S, A])(f: A => B): Free[S, B] = fa.map(f)
def flatMap[A, B](a: Free[S, A])(f: A => Free[S, B]): Free[S, B] = a.flatMap(f)
}

implicit def catsFreeDeferForFree[S[_]]: Defer[Free[S, ?]] =
new Defer[Free[S, ?]] {
def defer[A](fa: => Free[S, A]): Free[S, A] =
Free.defer(fa)
}
}

private trait FreeFoldable[F[_]] extends Foldable[Free[F, ?]] {
Expand Down
3 changes: 2 additions & 1 deletion free/src/test/scala/cats/free/FreeSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package free

import cats.arrow.FunctionK
import cats.data.EitherK
import cats.laws.discipline.{SemigroupalTests, FoldableTests, MonadTests, SerializableTests, TraverseTests}
import cats.laws.discipline.{SemigroupalTests, DeferTests, FoldableTests, MonadTests, SerializableTests, TraverseTests}
import cats.laws.discipline.arbitrary.catsLawsArbitraryForFn0
import cats.tests.CatsSuite

Expand All @@ -15,6 +15,7 @@ class FreeSuite extends CatsSuite {

implicit val iso = SemigroupalTests.Isomorphisms.invariant[Free[Option, ?]]

checkAll("Free[Option, ?]", DeferTests[Free[Option, ?]].defer[Int])
checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int])
checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]]))

Expand Down
36 changes: 36 additions & 0 deletions laws/src/main/scala/cats/laws/DeferLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cats
package laws

import catalysts.Platform
/**
* Laws that must be obeyed by any `Defer`.
*/
trait DeferLaws[F[_]] {
implicit def F: Defer[F]

def deferIdentity[A](fa: Unit => F[A]): IsEq[F[A]] =
F.defer(fa(())) <-> fa(())

def deferDoesNotEvaluate[A](fa: Unit => F[A]): IsEq[Boolean] = {
var evaluated = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsuspended vars in laws make me feel uneasy. First of all I fear that such laws will not have a long life.

To fix it, you can defer the whole thing:

  def deferDoesNotEvaluate[A](f: Boolean => F[A]): IsEq[Boolean] = {
    val lh = F.defer {
      var evaluated = false
      F.defer {
        evaluated = true
        f(evaluated)
      }
      f(evaluated)
    }
    lh <-> f(false)
  }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m nervous that if you pass a function that ignores the Boolean this is true trivially.

val deferUnit = F.defer {
evaluated = true;
fa(())
}
evaluated <-> false
}

def deferIsStackSafe[A](fa: Unit => F[A]): IsEq[F[A]] = {
def loop(c: Int): F[A] =
if (c <= 0) F.defer(fa(()))
else F.defer(loop(c - 1))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This law is interesting, but in case an F[_] provides such a powerful defer, what about the behavior of flatMap? Shouldn't flatMap also be stack safe if defer is?

Also the question on my mind would be: is defer any relevant without a Monad restriction? Or in other words, do we have any data type that can implement Defer, but not Monad?

Note that we also have a StackSafeMonad.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good question about making the law stronger: if there is a monad, flatMap must be stack safe. The function cases wouldn't pass this law.

As to what might not be a monad: command line parsing is often done with an applicative, not a monad (see optparse-applicative library in haskell or https://github.com/bkirwi/decline for a cats version). Those types can still have a lawful Defer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have Applicative examples without a Monad definition, I guess that's fine 👍


val cnt = if (Platform.isJvm) 20000 else 2000
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my experience these arbitrary values hurt for F[_] data types that have an expensive flatMap, for example streaming data types, because you're not talking of a single event being emitted, but multiple ones, per flatMap.

In Cats-Effect what I did was to introduce an iterations: Int in SyncLaws (see sample) and an implicit Parameters in SyncTests (see definition and usage). Otherwise those tests would choke for Monix's Iterant for example, especially when executed on Travis.

And in absence of that, I'd use smaller values:

val cnt = if (Platform.isJvm) 10000 else 1000

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t see a compelling reason why this is only 2x too large. Can you give an idea?

loop(cnt) <-> (fa(()))
}
}

object DeferLaws {
def apply[F[_]](implicit ev: Defer[F]): DeferLaws[F] =
new DeferLaws[F] { def F: Defer[F] = ev }
}
29 changes: 29 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/DeferTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cats
package laws
package discipline

import org.scalacheck.{Arbitrary, Prop}
import Prop._
import org.typelevel.discipline.Laws

trait DeferTests[F[_]] extends Laws {
def laws: DeferLaws[F]

def defer[A: Arbitrary](implicit
ArbFA: Arbitrary[F[A]],
EqFA: Eq[F[A]],
EqBool: Eq[Boolean]
): RuleSet = {
new DefaultRuleSet(
name = "defer",
parent = None,
"defer Identity" -> forAll(laws.deferIdentity[A] _),
"defer does not evaluate" -> forAll(laws.deferDoesNotEvaluate[A] _),
"defer is stack safe" -> forAll(laws.deferIsStackSafe[A] _))
}
}

object DeferTests {
def apply[F[_]: Defer]: DeferTests[F] =
new DeferTests[F] { def laws: DeferLaws[F] = DeferLaws[F] }
}
2 changes: 2 additions & 0 deletions tests/src/test/scala/cats/tests/EitherTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests, OrderTests, Par
class EitherTSuite extends CatsSuite {
implicit val iso = SemigroupalTests.Isomorphisms.invariant[EitherT[ListWrapper, String, ?]](EitherT.catsDataFunctorForEitherT(ListWrapper.functor))

checkAll("EitherT[Eval, String, ?]", DeferTests[EitherT[Eval, String, ?]].defer[Int])

{
checkAll("EitherT[Option, ListWrapper[String], ?]", SemigroupKTests[EitherT[Option, ListWrapper[String], ?]].semigroupK[Int])
checkAll("SemigroupK[EitherT[Option, ListWrapper[String], ?]]", SerializableTests.serializable(SemigroupK[EitherT[Option, ListWrapper[String], ?]]))
Expand Down
3 changes: 2 additions & 1 deletion tests/src/test/scala/cats/tests/EvalSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package cats
package tests

import cats.laws.ComonadLaws
import cats.laws.discipline.{BimonadTests, CommutativeMonadTests, SemigroupalTests, ReducibleTests, SerializableTests}
import cats.laws.discipline.{BimonadTests, CommutativeMonadTests, DeferTests, SemigroupalTests, ReducibleTests, SerializableTests}
import cats.laws.discipline.arbitrary._
import cats.kernel.laws.discipline.{EqTests, GroupTests, MonoidTests, OrderTests, PartialOrderTests, SemigroupTests}
import org.scalacheck.{Arbitrary, Cogen, Gen}
Expand Down Expand Up @@ -93,6 +93,7 @@ class EvalSuite extends CatsSuite {
checkAll("Eval[Int]", BimonadTests[Eval].bimonad[Int, Int, Int])
}

checkAll("Eval[Int]", DeferTests[Eval].defer[Int])
checkAll("Eval[Int]", CommutativeMonadTests[Eval].commutativeMonad[Int, Int, Int])
checkAll("CommutativeMonad[Eval]", SerializableTests.serializable(CommutativeMonad[Eval]))

Expand Down
22 changes: 22 additions & 0 deletions tests/src/test/scala/cats/tests/FunctionSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,35 @@ class FunctionSuite extends CatsSuite {
import Helpers._

checkAll("Function0[Int]", SemigroupalTests[Function0].semigroupal[Int, Int, Int])
// TODO: make an binary compatible way to do this
implicit val deferFunction0: Defer[Function0] =
Copy link
Contributor

@kailuowang kailuowang Jun 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to do this that I can think of is to just write this in a new cats.instance.FunctionInstancesBinaryCompatible1 trait and then add a new cats.instances.AllInstancesBinaryCompatible1 trait to extend it. And let cats.instances.all and cats.implicits extend it. (basically the same way as how we extend syntax while reserving binary compatibility)

new Defer[Function0] {
case class Deferred[A](fa: () => Function0[A]) extends Function0[A] {
def apply() = {
@annotation.tailrec
def loop(f: () => Function0[A]): A =
f() match {
case Deferred(f) => loop(f)
case next => next()
}
loop(fa)
}
}
def defer[A](fa: => Function0[A]): Function0[A] = {
lazy val cachedFa = fa
Deferred(() => cachedFa)
}
}
checkAll("Function0[Int]", DeferTests[Function0].defer[Int])
checkAll("Semigroupal[Function0]", SerializableTests.serializable(Semigroupal[Function0]))

checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int])
checkAll("Bimonad[Function0]", SerializableTests.serializable(Bimonad[Function0]))

implicit val iso = SemigroupalTests.Isomorphisms.invariant[Function1[Int, ?]]
checkAll("Function1[Int, Int]", SemigroupalTests[Function1[Int, ?]].semigroupal[Int, Int, Int])
// TODO: make an binary compatible way to do this
//checkAll("Function1[Int => ?]", DeferTests[Function1[Int, ?]].defer[Int])
checkAll("Semigroupal[Function1[Int, ?]]", SerializableTests.serializable(Semigroupal[Function1[Int, ?]]))

checkAll("Function1[Int, Int]", MonadTests[Int => ?].monad[Int, Int, Int])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ class ReaderWriterStateTSuite extends CatsSuite {
implicit val iso = SemigroupalTests.Isomorphisms
.invariant[IndexedReaderWriterStateT[ListWrapper, String, String, Int, String, ?]](IndexedReaderWriterStateT.catsDataFunctorForIRWST(ListWrapper.functor))

checkAll("IndexedReaderWriterStateT[Eval, String, String, Int, String, ?]",
DeferTests[IndexedReaderWriterStateT[Eval, String, String, Int, String, ?]].defer[Int])

{
implicit val F: Monad[ListWrapper] = ListWrapper.monad

Expand Down
2 changes: 2 additions & 0 deletions tests/src/test/scala/cats/tests/IndexedStateTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ class IndexedStateTSuite extends CatsSuite {
Profunctor[IndexedStateT[ListWrapper, ?, ?, String]]
}

checkAll("IndexedStateT[Eval, String, String, ?]", DeferTests[IndexedStateT[Eval, String, String, ?]].defer[Int])

{
// F needs a Monad to do Eq on StateT
implicit val F: Monad[ListWrapper] = ListWrapper.monad
Expand Down
4 changes: 3 additions & 1 deletion tests/src/test/scala/cats/tests/IorTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import cats.laws.discipline.arbitrary._

class IorTSuite extends CatsSuite {

checkAll("IorT[Eval, String, ?]", DeferTests[IorT[Eval, String, ?]].defer[Int])

{
implicit val F = ListWrapper.functor

Expand Down Expand Up @@ -352,4 +354,4 @@ class IorTSuite extends CatsSuite {
IorT.condF(test, optionS, optionI) === (if (test) IorT.right(optionS) else IorT.left(optionI))
}
}
}
}
3 changes: 2 additions & 1 deletion tests/src/test/scala/cats/tests/KleisliSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import cats.laws.discipline.arbitrary._
import cats.laws.discipline.eq._
import org.scalacheck.Arbitrary
import cats.kernel.laws.discipline.{MonoidTests, SemigroupTests}
import cats.laws.discipline.{MonoidKTests, SemigroupKTests}
import cats.laws.discipline.{MonoidKTests, DeferTests, SemigroupKTests}
import Helpers.CSemi
import catalysts.Platform

Expand All @@ -32,6 +32,7 @@ class KleisliSuite extends CatsSuite {
checkAll("ApplicativeError[Kleisli[Option, Int, Int], Unit]", SerializableTests.serializable(instance))
}

checkAll("Kleisli[Eval, Int, ?]", DeferTests[Kleisli[Eval, Int, ?]].defer[Int])
checkAll("Kleisli[Option, Int, Int] with Unit", MonadErrorTests[Kleisli[Option, Int, ?], Unit].monadError[Int, Int, Int])
checkAll("MonadError[Kleisli[Option, Int, Int], Unit]", SerializableTests.serializable(MonadError[Kleisli[Option, Int, ?], Unit]))

Expand Down
2 changes: 2 additions & 0 deletions tests/src/test/scala/cats/tests/NestedSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class NestedSuite extends CatsSuite {
implicit override val generatorDrivenConfig: PropertyCheckConfiguration =
PropertyCheckConfiguration(minSuccessful = 20, sizeRange = 5)

checkAll("Nested[Eval, List, ?]", DeferTests[Nested[Eval, List, ?]].defer[Int])

{
// Invariant composition
implicit val instance = ListWrapper.invariant
Expand Down
Loading