diff --git a/core/src/main/scala/cats/data/Kleisli.scala b/core/src/main/scala/cats/data/Kleisli.scala index ad3b1f86cd..43939e81e8 100644 --- a/core/src/main/scala/cats/data/Kleisli.scala +++ b/core/src/main/scala/cats/data/Kleisli.scala @@ -83,7 +83,11 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self => def apply(a: A): F[B] = run(a) } -object Kleisli extends KleisliInstances with KleisliFunctions with KleisliExplicitInstances { +object Kleisli + extends KleisliInstances + with KleisliFunctions + with KleisliFunctionsBinCompat + with KleisliExplicitInstances { /** * Internal API — shifts the execution of `run` in the `F` context. @@ -143,6 +147,31 @@ sealed private[data] trait KleisliFunctions { Kleisli(f.andThen(fa.run)) } +sealed private[data] trait KleisliFunctionsBinCompat { + + /** + * Lifts a natural transformation of effects within a Kleisli + * to a transformation of Kleislis. + * + * Equivalent to running `mapK(f) on a Kleisli. + * + * {{{ + * scala> import cats._, data._ + * scala> val f: (List ~> Option) = λ[List ~> Option](_.headOption) + * + * scala> val k: Kleisli[List, String, Char] = Kleisli(_.toList) + * scala> k.run("foo") + * res0: List[Char] = List(f, o, o) + * + * scala> val k2: Kleisli[Option, String, Char] = Kleisli.liftFunctionK(f)(k) + * scala> k2.run("foo") + * res1: Option[Char] = Some(f) + * }}} + * */ + def liftFunctionK[F[_], G[_], A](f: F ~> G): Kleisli[F, A, ?] ~> Kleisli[G, A, ?] = + λ[Kleisli[F, A, ?] ~> Kleisli[G, A, ?]](_.mapK(f)) +} + sealed private[data] trait KleisliExplicitInstances { def endoSemigroupK[F[_]](implicit FM: FlatMap[F]): SemigroupK[λ[α => Kleisli[F, α, α]]] = diff --git a/tests/src/test/scala/cats/tests/KleisliSuite.scala b/tests/src/test/scala/cats/tests/KleisliSuite.scala index f3713ed477..f2f79300b4 100644 --- a/tests/src/test/scala/cats/tests/KleisliSuite.scala +++ b/tests/src/test/scala/cats/tests/KleisliSuite.scala @@ -231,6 +231,13 @@ class KleisliSuite extends CatsSuite { } } + test("liftFunctionK consistent with mapK") { + val t: List ~> Option = λ[List ~> Option](_.headOption) + forAll { (f: Kleisli[List, Int, Int], i: Int) => + (f.mapK(t).run(i)) should ===(Kleisli.liftFunctionK(t)(f).run(i)) + } + } + test("flatMapF") { forAll { (f: Kleisli[List, Int, Int], t: Int => List[Int], i: Int) => f.run(i).flatMap(t) should ===(f.flatMapF(t).run(i))