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

added TFunctor #1815

Closed
wants to merge 7 commits into from
Closed
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
42 changes: 42 additions & 0 deletions core/src/main/scala/cats/TFunctor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cats

import simulacrum.typeclass

/**
* This is an endofunctor in the category of endofunctors in `Skal`.
*
* `Skal` is the category of scala types. Functors in `Skal`
* is encoded as `Functor`. The functors in `Skal` themselves forms
* a category, let's denote it as `F[Skal]`.
* In `F[Skal]`, functors of Skal, e.g. `Option[_]` and `Either[E, _]`, are objects,
* while natural transformations, e.g. `Option ~> Either[E, ?]`, are arrows.
* A endofunctor in `F[Skal]` maps one set of functors of `Skal` to another
* set of functors of `Skal` while preserving the structures.
*
* For `TFunctor`, the domain is `F[_]`, the codomain is `T[F, _]`, both are
* functors in `Skal`. The `TFunctor` provides a mapping from the arrows between
* `F[_]` and `G[_]`, i.e. `F ~> G` to arrows between `T[F, _]` and `T[G, _]`,
* i.e. `T[F, ?] ~> T[G, ?]`. The `lift` method makes this intention clear.
*
* In `cats.core`, examples of such `TFunctor`s are monad transformers such
* as `OptionT`, `EitherT`
*
*/
@typeclass trait TFunctor[T[_[_], _]] {
Copy link
Contributor

Choose a reason for hiding this comment

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

do we need T[_[_], _] or would T[_[_]] do? I'm not sure what the second parameter is buying us. Maybe I'm missing it. Can't we just ignore the inner value type? Also, wouldn't we have more flexibility only acting on T[_[_]]?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed here. If we had T[_[_]] I could come up with a lot more usecases. Maybe call it FunctorK then. We'd have to wrap the transformers in Forall though, which may mean our unified transform syntax fails to be found implicitly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The main reason I chose T[_[_], _] here, is that given any Functor F[_], T[F, _] is also a Functor. So T[_[_], _] can be deemed as a functor between the F[_] Functor and the T[F, _] Functor. i.e. a endofunctor in the category of endofunctors, thus more like a higher kinded Functor.
If you use T[_[_]], then given any Functor F[_], T[F] isn't a Functor, it's a kind * type, not a type constructor. So T[_[_]] is a functor between F[_] Functor and kind * type T[F]s. That makes it less a high kinded Functor. However, you can work around with the parametricity in instance definitions, e.g. for OptionT,

def functorKInstance[A]: FunctorK[OptionT[?[_], A]]

With parametric A, we can have a FunctorK instance forall As. In some sense, we may choose to see this set of FunctorK instances as a single truely higher kinded functor.

Practical use wise, I can't think of any downside of using T[_[_]]. The only downside is its awkward encoding of the higher kinded Functor - it relies on instance definition. It may have more usecases, one that immediately jumps out is FunctionK. So maybe practically it's more useful.

Now, the fact that we aren't sure about this thing makes me think that maybe we shouldn't place this in cats-core. I already have FunctorK[T[_]] defined in mainecoon, I could add all these instances there. Or we can create a new cats-extra module for this type of less commonly used type class.

Copy link
Contributor

Choose a reason for hiding this comment

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

With the example of FunctionK, my first thought is that that’s what’s defined incorrectly. We really want FunctionK[F[_], G[_], A] and Forall[FunctionK[F, G, ?]] for a natural transformation. Then you do have a FunctorK[FunctionK[F, ?[_], ?]]. But that might just be crazy talk.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if "higher-kinded X" is a really rigid term. In my mind, higher-kinded functor could equally well mean a functor from (endofunctors in Scal) to Scal, or a functor from endofunctors in Scal to other endofunctors in Scal. This is my main problem with the -K naming scheme; it gets even worse when type classes have multiple type parameters.

As well, the encoding proposed above does not reify that the A can vary with the instance remaining valid. The consumer of the instance needs a Forall[Lambda[A =>FunctorK[OptionT[?[_], A]]]] to have that fact in hand, and that makes it much more unwieldy, though a type alias could help.

To my mind if we're going to do a cats-extras project we'll want this there.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with @kailuowang and @edmundnoble, I don't think it's all that useful in cats-core. I'd like to see conformity in Monad Transformers for defining a mapK method, but it doesn't need a type class in core IMO.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if "higher-kinded X" is a really rigid term.

Absolutely. I don’t think it helps at all. But if Functor is really “endofunctor in the category of Scal”, I’d expect FunctorK to mean the same thing as K in other instances, which basically adds “endofunctors” to the description – i.e., “endofunctor in the category of endofunctors in Scal”.

I’d also be happy with Functor being renamed to Endofunctor and having EndofunctorK and something like LowerFunctor (for a functor from the category of endofunctors to the category of Skal).

Also agreed that none of this should be in cats-core. I would like to find some time to work with -Ykind-polymorphism and to define these things as specializations of kind-polymorphic endofunctors, etc.

This sort of thing has been started in a few different libraries already – khats (which is too homophonically-named to be useful), Griffins (which never really got off the ground and mostly lives inside a Matryoshka branch), and I think one other that I have forgotten 😕

def mapNT[F[_], G[_], A](h: T[F, A])(f: F ~> G): T[G, A]

/**
* Lift a `F ~> G` to a `T[F, ?] ~> T[G, ?]`.
*
* {{{
* scala> import cats.implicits._, cats.data.OptionT
* scala> val lv: List ~> Vector = λ[List ~> Vector](_.toVector)
* scala> val olv: OptionT[List, ?] ~> OptionT[Vector, ?] = TFunctor[OptionT].liftNT(lv)
* scala> olv(OptionT.liftF(List(1)))
* res0: OptionT[Vector, Int] = OptionT(Vector(Some(1)))
* }}}
*/
def liftNT[F[_], G[_]](f: F ~> G): T[F, ?] ~> T[G, ?] =
λ[T[F, ?] ~> T[G, ?]](hf => mapNT(hf)(f))
}

5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/EitherK.scala
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ private[data] sealed abstract class EitherKInstances0 extends EitherKInstances1

private[data] sealed abstract class EitherKInstances extends EitherKInstances0 {

implicit def catsDataTFunctorForEitherK[L[_]]: TFunctor[EitherK[L, ?[_], ?]] = new TFunctor[EitherK[L, ?[_], ?]] {
def mapNT[F[_], G[_], A](ek: EitherK[L, F, A])(f: F ~> G): EitherK[L, G, A] = EitherK(ek.run.map(f.apply))
}


implicit def catsDataComonadForEitherK[F[_], G[_]](implicit F0: Comonad[F], G0: Comonad[G]): Comonad[EitherK[F, G, ?]] =
new EitherKComonad[F, G] {
implicit def F: Comonad[F] = F0
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 @@ -420,6 +420,11 @@ object EitherT extends EitherTInstances {

private[data] abstract class EitherTInstances extends EitherTInstances1 {

implicit def catsDataTFunctorForEitherT[A]: TFunctor[EitherT[?[_], A, ?]] = new TFunctor[EitherT[?[_], A, ?]] {
def mapNT[F[_], G[_], B](h: EitherT[F, A, B])(f: F ~> G): EitherT[G, A, B] = EitherT(f(h.value))
}


implicit def catsDataOrderForEitherT[F[_], L, R](implicit F: Order[F[Either[L, R]]]): Order[EitherT[F, L, R]] =
new EitherTOrder[F, L, R] {
val F0: Order[F[Either[L, R]]] = F
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/Func.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ object Func extends FuncInstances {
}

private[data] abstract class FuncInstances extends FuncInstances0 {

implicit def catsDataTFunctorForFunc[A]: TFunctor[Func[?[_], A, ?]] = new TFunctor[Func[?[_], A, ?]] {
def mapNT[F[_], G[_], B](h: Func[F, A, B])(f: F ~> G): Func[G, A, B] = Func.func(h.run.andThen(f.apply))
}

implicit def catsDataApplicativeForFunc[F[_], C](implicit FF: Applicative[F]): Applicative[λ[α => Func[F, C, α]]] =
new FuncApplicative[F, C] {
def F: Applicative[F] = FF
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/data/IdT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ private[data] sealed abstract class IdTInstances0 extends IdTInstances1 {

private[data] sealed abstract class IdTInstances extends IdTInstances0 {

implicit val catsDataTFunctorForIdT: TFunctor[IdT] = new TFunctor[IdT] {
def mapNT[F[_], G[_], A](h: IdT[F, A])(f: F ~> G): IdT[G, A] = IdT(f(h.value))
}

implicit def catsDataNonEmptyTraverseForIdT[F[_]](implicit F: NonEmptyTraverse[F]): NonEmptyTraverse[IdT[F, ?]] =
new IdTNonEmptyTraverse[F] { implicit val F0: NonEmptyTraverse[F] = F }

Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ private[data] sealed trait KleisliFunctions {
}

private[data] sealed abstract class KleisliInstances extends KleisliInstances0 {

implicit def catsDataTFunctorForKleisli[A]: TFunctor[Kleisli[?[_], A, ?]] = new TFunctor[Kleisli[?[_], A, ?]] {
def mapNT[F[_], G[_], B](k: Kleisli[F, A, B])(f: F ~> G): Kleisli[G, A, B] = k.transform(f)
}

implicit def catsDataCommutativeMonadForKleisli[F[_], A, B](implicit F0: CommutativeMonad[F]): CommutativeMonad[Kleisli[F, A, ?]] =
new KleisliMonad[F, A] with CommutativeMonad[Kleisli[F, A, ?]] {
implicit def F: Monad[F] = F0
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 @@ -28,6 +28,12 @@ final case class Nested[F[_], G[_], A](value: F[G[A]])
object Nested extends NestedInstances

private[data] sealed abstract class NestedInstances extends NestedInstances0 {


implicit def catsDataTFunctorForNested[I[_]]: TFunctor[Nested[?[_], I, ?]] = new TFunctor[Nested[?[_], I, ?]] {
def mapNT[F[_], G[_], A](n: Nested[F, I, A])(f: F ~> G): Nested[G, I, A] = Nested(f(n.value))
}

implicit def catsDataEqForNested[F[_], G[_], A](implicit FGA: Eq[F[G[A]]]): Eq[Nested[F, G, A]] =
FGA.on(_.value)

Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/data/OneAnd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) {

private[data] sealed trait OneAndInstances extends OneAndLowPriority3 {

implicit val catsDataTFunctorForOneAnd: TFunctor[OneAnd] = new TFunctor[OneAnd] {
def mapNT[F[_], G[_], A](o: OneAnd[F, A])(f: F ~> G): OneAnd[G, A] = OneAnd(o.head, f(o.tail))
}

implicit def catsDataEqForOneAnd[A, F[_]](implicit A: Eq[A], FA: Eq[F[A]]): Eq[OneAnd[F, A]] =
new Eq[OneAnd[F, A]]{
def eqv(x: OneAnd[F, A], y: OneAnd[F, A]): Boolean = x === y
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ object OptionT extends OptionTInstances {
}

private[data] sealed trait OptionTInstances extends OptionTInstances0 {

implicit val catsDataTFunctorForOptionT: TFunctor[OptionT] = new TFunctor[OptionT] {
def mapNT[F[_], G[_], A](h: OptionT[F, A])(f: F ~> G): OptionT[G, A] = OptionT(f(h.value))
}

implicit def catsDataMonadForOptionT[F[_]](implicit F0: Monad[F]): Monad[OptionT[F, ?]] =
new OptionTMonad[F] { implicit val F = F0 }

Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/Tuple2K.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ final case class Tuple2K[F[_], G[_], A](first: F[A], second: G[A])
object Tuple2K extends Tuple2KInstances

private[data] sealed abstract class Tuple2KInstances extends Tuple2KInstances0 {

implicit def catsDataTFunctorForTuple2K[L[_]]: TFunctor[Tuple2K[L, ?[_], ?]] = new TFunctor[Tuple2K[L, ?[_], ?]] {
def mapNT[F[_], G[_], A](ek: Tuple2K[L, F, A])(f: F ~> G): Tuple2K[L, G, A] = Tuple2K(ek.first, f(ek.second))
}

implicit def catsDataOrderForTuple2K[F[_], G[_], A](implicit FF: Order[F[A]], GF: Order[G[A]]): Order[Tuple2K[F, G, A]] = new Tuple2KOrder[F, G, A] {
def F: Order[F[A]] = FF
def G: Order[G[A]] = GF
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/data/WriterT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ object WriterT extends WriterTInstances with WriterTFunctions {
}

private[data] sealed abstract class WriterTInstances extends WriterTInstances0 {

implicit def catsDataTFunctorForWriterT[A]: TFunctor[WriterT[?[_], A, ?]] = new TFunctor[WriterT[?[_], A, ?]] {
def mapNT[F[_], G[_], B](w: WriterT[F, A, B])(f: F ~> G): WriterT[G, A, B] = WriterT(f(w.run))
}

implicit def catsDataCommutativeMonadForWriterT[F[_], L](implicit F: CommutativeMonad[F], L: CommutativeMonoid[L]): CommutativeMonad[WriterT[F, L, ?]] =
new WriterTMonad[F, L] with CommutativeMonad[WriterT[F, L, ?]] {
implicit val F0: Monad[F] = F
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/all.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ trait AllSyntax
with SemigroupKSyntax
with ShowSyntax
with StrongSyntax
with TFunctorSyntax
with TraverseSyntax
with NonEmptyTraverseSyntax
with ValidatedSyntax
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/cats/syntax/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ package object syntax {
object semigroupk extends SemigroupKSyntax
object show extends ShowSyntax
object strong extends StrongSyntax
object tfunctor extends TFunctorSyntax
object traverse extends TraverseSyntax
object nonEmptyTraverse extends NonEmptyTraverseSyntax
object validated extends ValidatedSyntax
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/syntax/tfunctor.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package cats
package syntax

trait TFunctorSyntax extends TFunctor.ToTFunctorOps
15 changes: 15 additions & 0 deletions free/src/main/scala/cats/free/Free.scala
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,17 @@ sealed abstract class Free[S[_], A] extends Product with Serializable {
case FlatMapped(c, g) => M.map(c.foldMap(f))(cc => Left(g(cc)))
})

/**
* Changes the underlying `Monad` for this `Free`, ie.
* turning this `Free[S, A]` into a `Free[N, A]`.
*/
def hoist[N[_]](mn: FunctionK[S, N]): Free[N, A] =
step match {
case Pure(a) => Pure(a)
case Suspend(m) => Suspend(mn(m))
case FlatMapped(c, g) => FlatMapped(c.hoist(mn), g.andThen(_.hoist(mn)))
}

/**
* Compile your free monad into another language by changing the
* suspension functor using the given natural transformation `f`.
Expand Down Expand Up @@ -249,4 +260,8 @@ object Free {
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 val catsFreeTFunctorForFree: TFunctor[Free] = new TFunctor[Free] {
def mapNT[F[_], G[_], A](h: Free[F, A])(f: F ~> G): Free[G, A] = h.hoist(f)
}
}
6 changes: 5 additions & 1 deletion free/src/main/scala/cats/free/FreeT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cats
package free

import scala.annotation.tailrec

import cats.arrow.FunctionK

/**
Expand Down Expand Up @@ -183,6 +182,11 @@ object FreeT extends FreeTInstances {
}

private[free] sealed trait FreeTInstances extends FreeTInstances0 {

implicit def catsFreeTFunctorForFreeT[S[_]]: TFunctor[FreeT[S, ?[_], ?]] = new TFunctor[FreeT[S, ?[_], ?]] {
def mapNT[F[_], G[_], A](ft: FreeT[S, F, A])(f: F ~> G): FreeT[S, G, A] = ft.hoist(f)
}

implicit def catsFreeMonadErrorForFreeT[S[_], M[_], E](implicit E: MonadError[M, E]): MonadError[FreeT[S, M, ?], E] =
new MonadError[FreeT[S, M, ?], E] with FreeTMonad[S, M] {
override def M = E
Expand Down
12 changes: 11 additions & 1 deletion free/src/test/scala/cats/free/FreeTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ import org.scalacheck.{Arbitrary, Gen, Cogen}
class FreeTTests extends CatsSuite {

import FreeTTests._
{
implicit val freeOptionListEq: Eq[FreeT[Option, List, Int]] = new Eq[FreeT[Option, List, Int]]{
val ol = λ[Option ~> List](_.toList)
def eqv(a: FreeT[Option, List, Int], b: FreeT[Option, List, Int]) = a.runM(o => ol(o)) === b.runM(o => ol(o))
}

checkAll("FreeT[Option, ?[_], ?]", TFunctorTests[FreeT[Option, ?[_], ?]].tfunctor[List, Vector, Option, Int])
checkAll("TFunctor[FreeT[Option, ?[_], ?]]", SerializableTests.serializable(TFunctor[FreeT[Option, ?[_], ?]]))
}

{
implicit val freeTFlatMap: FlatMap[FreeTOption] = FreeT.catsFreeFlatMapForFreeT[Option, Option]
Expand Down Expand Up @@ -199,11 +208,12 @@ trait FreeTTestsInstances {

implicit def intStateArb[A: Arbitrary]: Arbitrary[IntState[A]] = catsLawArbitraryForState[Int, A]

implicit def freeTOptionEq[A](implicit A: Eq[A], OM: Monad[Option]): Eq[FreeTOption[A]] = new Eq[FreeTOption[A]] {
implicit def freeTOptionEq[A](implicit A: Eq[A]): Eq[FreeTOption[A]] = new Eq[FreeTOption[A]] {
def eqv(a: FreeTOption[A], b: FreeTOption[A]) = Eq[Option[A]].eqv(a.runM(identity), b.runM(identity))
}

implicit def freeTStateEq[A](implicit A: Eq[A], SM: Monad[IntState]): Eq[FreeTState[A]] = new Eq[FreeTState[A]] {
def eqv(a: FreeTState[A], b: FreeTState[A]) = Eq[IntState[A]].eqv(a.runM(identity)(SM, SM), b.runM(identity)(SM, SM))
}
}

10 changes: 6 additions & 4 deletions free/src/test/scala/cats/free/FreeTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ package free

import cats.arrow.FunctionK
import cats.data.EitherK
import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests}
import cats.laws.discipline.arbitrary.catsLawsArbitraryForFn0
import cats.laws.discipline.{CartesianTests, MonadTests, SerializableTests, TFunctorTests}
import cats.laws.discipline.arbitrary._
import cats.tests.CatsSuite

import org.scalacheck.{Arbitrary, Gen, Cogen}
import org.scalacheck.{Arbitrary, Cogen, Gen}
import Arbitrary.arbFunction1

class FreeTests extends CatsSuite {
import FreeTests._

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

checkAll("Free", TFunctorTests[Free].tfunctor[List, Vector, Option, Int])
checkAll("TFunctor[Free]", SerializableTests.serializable(TFunctor[Free]))

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

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


import cats.arrow.FunctionK
import syntax.all._
import cats.~>

trait TFunctorLaws[T[_[_], _]]{
implicit def T: TFunctor[T]

def covariantIdentity[F[_], A](fg: T[F, A]): IsEq[T[F, A]] =
fg.mapNT(FunctionK.id[F]) <-> fg

def covariantComposition[F[_], G[_], H[_], A](fa: T[F, A], f: F ~> G, g: G ~> H): IsEq[T[H, A]] =
fa.mapNT(f).mapNT(g) <-> fa.mapNT(f andThen g)

}

object TFunctorLaws {
def apply[T[_[_], _]](implicit ev: TFunctor[T]): TFunctorLaws[T] =
new TFunctorLaws[T] { def T: TFunctor[T] = ev }
}

11 changes: 11 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,17 @@ object arbitrary extends ArbitraryInstances0 {
implicit def catsLawsArbitraryForReaderWriterStateT[F[_]: Applicative, E, L, S, A](implicit F: Arbitrary[(E, S) => F[(L, S, A)]]): Arbitrary[ReaderWriterStateT[F, E, L, S, A]] =
Arbitrary(F.arbitrary.map(ReaderWriterStateT(_)))

implicit val catsLawArbitraryForFunctionKOptionList: Arbitrary[Option ~> List] = Arbitrary(Gen.const(λ[Option ~> List](_.toList)))

implicit val catsLawArbitraryForFunctionKListOption: Arbitrary[List ~> Option] = Arbitrary(Gen.const(λ[List ~> Option](_.headOption)))

implicit val catsLawArbitraryForFunctionKListVector: Arbitrary[List ~> Vector] = Arbitrary(Gen.const(λ[List ~> Vector](_.toVector)))

implicit val catsLawArbitraryForFunctionKVectorList: Arbitrary[Vector ~> List] = Arbitrary(Gen.const(λ[Vector ~> List](_.toList)))

implicit val catsLawArbitraryForFunctionKVectorOption: Arbitrary[Vector ~> Option] = Arbitrary(Gen.const(λ[Vector ~> Option](_.headOption)))

implicit val catsLawArbitraryForFunctionKOptionVector: Arbitrary[Option ~> Vector] = Arbitrary(Gen.const(λ[Option ~> Vector](_.toVector)))
}

private[discipline] sealed trait ArbitraryInstances0 {
Expand Down
37 changes: 37 additions & 0 deletions laws/src/main/scala/cats/laws/discipline/TFunctorTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cats
package laws
package discipline


import org.scalacheck.Arbitrary
import org.scalacheck.Prop._
import cats.{Eq, ~>}
import org.typelevel.discipline.Laws

trait TFunctorTests[T[_[_], _]] extends Laws {
def laws: TFunctorLaws[T]

def tfunctor[F[_], G[_], H[_], A: Arbitrary](implicit
ArbFA: Arbitrary[T[F, A]],
ArbitraryG: Arbitrary[F[A]],
ArbitraryH: Arbitrary[G[A]],
ArbitraryI: Arbitrary[H[A]],
ArbitraryFK: Arbitrary[F ~> G],
ArbitraryFK2: Arbitrary[G ~> H],
ArbitraryFK3: Arbitrary[G ~> F],
ArbitraryFK4: Arbitrary[H ~> G],
EqFA: Eq[T[F, A]],
EqFC: Eq[T[H, A]]
): RuleSet = {
new DefaultRuleSet(
name = "TFunctor",
parent = None,
"covariant identity" -> forAll(laws.covariantIdentity[F, A] _),
"covariant composition" -> forAll(laws.covariantComposition[F, G, H, A] _))
}
}

object TFunctorTests {
def apply[T[_[_], _]: TFunctor]: TFunctorTests[T] =
new TFunctorTests[T] { def laws: TFunctorLaws[T] = TFunctorLaws[T] }
}
7 changes: 5 additions & 2 deletions tests/src/test/scala/cats/tests/EitherKTests.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cats.tests
package cats
package tests

import cats._
import cats.kernel.laws.OrderLaws
import cats.data.EitherK
import cats.functor.Contravariant
Expand All @@ -10,6 +10,9 @@ import cats.laws.discipline.eq._

class EitherKTests extends CatsSuite {

checkAll("EitherK[Option, ?[_], ?]", TFunctorTests[EitherK[Option, ?[_], ?]].tfunctor[List, Vector, Option, Int])
checkAll("TFunctor[EitherK[Option, ?[_], ?]]", SerializableTests.serializable(TFunctor[EitherK[Option, ?[_], ?]]))

checkAll("EitherK[Option, Option, ?]", TraverseTests[EitherK[Option, Option, ?]].traverse[Int, Int, Int, Int, Option, Option])
checkAll("Traverse[EitherK[Option, Option, ?]]", SerializableTests.serializable(Traverse[EitherK[Option, Option, ?]]))

Expand Down
3 changes: 3 additions & 0 deletions tests/src/test/scala/cats/tests/EitherTTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import cats.kernel.laws.{GroupLaws, OrderLaws}
class EitherTTests extends CatsSuite {
implicit val iso = CartesianTests.Isomorphisms.invariant[EitherT[ListWrapper, String, ?]](EitherT.catsDataFunctorForEitherT(ListWrapper.functor))

checkAll("EitherT[?[_], String, ?]", TFunctorTests[EitherT[?[_], String, ?]].tfunctor[List, Vector, Option, Int])
checkAll("TFunctor[EitherT[?[_], String, ?]]", SerializableTests.serializable(TFunctor[EitherT[?[_], String, ?]]))

{
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
Loading