Skip to content

Commit 94f4928

Browse files
author
LukaJCB
authored
Finish adding mapK (#2000)
* Add mapK to most (hopefully all) transformers * Add mapK to free related constructs * Deprecate Kleisli#transform in favor of mapK * Fix tests file name * Add missing mapK methods * Don't deprecate Free methods and use mapK for parallel kleisli instance
1 parent f3981dd commit 94f4928

25 files changed

+193
-40
lines changed

core/src/main/scala/cats/data/EitherK.scala

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ final case class EitherK[F[_], G[_], A](run: Either[F[A], G[A]]) {
1616
def map[B](f: A => B)(implicit F: Functor[F], G: Functor[G]): EitherK[F, G, B] =
1717
EitherK(run.bimap(F.lift(f), G.lift(f)))
1818

19+
/**
20+
* Modify the right side context `G` using transformation `f`.
21+
*/
22+
def mapK[H[_]](f: G ~> H): EitherK[F, H, A] =
23+
EitherK(run.map(f.apply))
24+
1925
def coflatMap[B](f: EitherK[F, G, A] => B)(implicit F: CoflatMap[F], G: CoflatMap[G]): EitherK[F, G, B] =
2026
EitherK(
2127
run.bimap(a => F.coflatMap(a)(x => f(leftc(x))), a => G.coflatMap(a)(x => f(rightc(x))))
@@ -234,4 +240,3 @@ private[data] trait EitherKComonad[F[_], G[_]] extends Comonad[EitherK[F, G, ?]]
234240
def extract[A](p: EitherK[F, G, A]): A =
235241
p.extract
236242
}
237-

core/src/main/scala/cats/data/EitherT.scala

+5
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {
9191

9292
def map[D](f: B => D)(implicit F: Functor[F]): EitherT[F, A, D] = bimap(identity, f)
9393

94+
/**
95+
* Modify the context `F` using transformation `f`.
96+
*/
97+
def mapK[G[_]](f: F ~> G): EitherT[G, A, B] = EitherT[G, A, B](f(value))
98+
9499
def semiflatMap[D](f: B => F[D])(implicit F: Monad[F]): EitherT[F, A, D] =
95100
flatMap(b => EitherT.right(f(b)))
96101

core/src/main/scala/cats/data/Func.scala

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ sealed abstract class Func[F[_], A, B] { self =>
1212
def run: A => F[B]
1313
def map[C](f: B => C)(implicit FF: Functor[F]): Func[F, A, C] =
1414
Func.func(a => FF.map(self.run(a))(f))
15+
16+
/**
17+
* Modify the context `F` using transformation `f`.
18+
*/
19+
def mapK[G[_]](f: F ~> G): Func[G, A, B] =
20+
Func.func(run andThen f.apply)
1521
}
1622

1723
object Func extends FuncInstances {

core/src/main/scala/cats/data/IdT.scala

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ final case class IdT[F[_], A](value: F[A]) {
99
def map[B](f: A => B)(implicit F: Functor[F]): IdT[F, B] =
1010
IdT(F.map(value)(f))
1111

12+
/**
13+
* Modify the context `F` using transformation `f`.
14+
*/
15+
def mapK[G[_]](f: F ~> G): IdT[G, A] =
16+
IdT[G, A](f(value))
17+
1218
def flatMap[B](f: A => IdT[F, B])(implicit F: FlatMap[F]): IdT[F, B] =
1319
IdT(F.flatMap(value)(f.andThen(_.value)))
1420

core/src/main/scala/cats/data/IndexedReaderWriterStateT.scala

+7
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ final class IndexedReaderWriterStateT[F[_], E, L, SA, SB, A](val runF: F[(E, SA)
5252
def map[B](f: A => B)(implicit F: Functor[F]): IndexedReaderWriterStateT[F, E, L, SA, SB, B] =
5353
transform { (l, s, a) => (l, s, f(a)) }
5454

55+
/**
56+
* Modify the context `F` using transformation `f`.
57+
*/
58+
def mapK[G[_]](f: F ~> G)(implicit F: Functor[F]): IndexedReaderWriterStateT[G, E, L, SA, SB, A] =
59+
IndexedReaderWriterStateT.applyF(
60+
f(F.map(runF)(rwsa => (e, sa) => f(rwsa(e, sa)))))
61+
5562
/**
5663
* Modify the resulting state using `f` and the resulting value using `g`.
5764
*/

core/src/main/scala/cats/data/IndexedStateT.scala

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ final class IndexedStateT[F[_], SA, SB, A](val runF: F[SA => F[(SB, A)]]) extend
3939
def map[B](f: A => B)(implicit F: Functor[F]): IndexedStateT[F, SA, SB, B] =
4040
transform { case (s, a) => (s, f(a)) }
4141

42+
/**
43+
* Modify the context `F` using transformation `f`.
44+
*/
45+
def mapK[G[_]](f: F ~> G)(implicit F: Functor[F]): IndexedStateT[G, SA, SB, A] =
46+
IndexedStateT.applyF(
47+
f(F.map(runF)(_.andThen(fsa => f(fsa)))))
48+
4249
def contramap[S0](f: S0 => SA)(implicit F: Functor[F]): IndexedStateT[F, S0, SB, A] =
4350
IndexedStateT.applyF {
4451
F.map(runF) { safsba =>

core/src/main/scala/cats/data/Kleisli.scala

+10-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self =>
2121
def mapF[N[_], C](f: F[B] => N[C]): Kleisli[N, A, C] =
2222
Kleisli(run andThen f)
2323

24+
/**
25+
* Modify the context `F` using transformation `f`.
26+
*/
27+
def mapK[G[_]](f: F ~> G): Kleisli[G, A, B] =
28+
Kleisli[G, A, B](run andThen f.apply)
29+
2430
def flatMap[C](f: B => Kleisli[F, A, C])(implicit F: FlatMap[F]): Kleisli[F, A, C] =
2531
Kleisli((r: A) => F.flatMap[B, C](run(r))((b: B) => f(b).run(r)))
2632

@@ -48,8 +54,9 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self =>
4854
def local[AA](f: AA => A): Kleisli[F, AA, B] =
4955
Kleisli(f.andThen(run))
5056

57+
@deprecated("Use mapK", "1.0.0")
5158
def transform[G[_]](f: FunctionK[F, G]): Kleisli[G, A, B] =
52-
Kleisli(a => f(run(a)))
59+
mapK(f)
5360

5461
def lower(implicit F: Applicative[F]): Kleisli[F, A, F[B]] =
5562
Kleisli(a => F.pure(run(a)))
@@ -148,10 +155,10 @@ private[data] sealed abstract class KleisliInstances1 extends KleisliInstances2
148155
def monad: Monad[Kleisli[M, A, ?]] = catsDataMonadForKleisli
149156

150157
def sequential: Kleisli[F, A, ?] ~> Kleisli[M, A, ?] =
151-
λ[Kleisli[F, A, ?] ~> Kleisli[M, A, ?]](_.transform(P.sequential))
158+
λ[Kleisli[F, A, ?] ~> Kleisli[M, A, ?]](_.mapK(P.sequential))
152159

153160
def parallel: Kleisli[M, A, ?] ~> Kleisli[F, A, ?] =
154-
λ[Kleisli[M, A, ?] ~> Kleisli[F, A, ?]](_.transform(P.parallel))
161+
λ[Kleisli[M, A, ?] ~> Kleisli[F, A, ?]](_.mapK(P.parallel))
155162
}
156163
}
157164

core/src/main/scala/cats/data/Nested.scala

+9-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@ package data
2323
* res1: List[Option[String]] = List(Some(2), None)
2424
* }}}
2525
*/
26-
final case class Nested[F[_], G[_], A](value: F[G[A]])
26+
final case class Nested[F[_], G[_], A](value: F[G[A]]) {
27+
28+
/**
29+
* Modify the context `F` using transformation `f`.
30+
*/
31+
def mapK[H[_]](f: F ~> H): Nested[H, G, A] =
32+
Nested(f(value))
33+
34+
}
2735

2836
object Nested extends NestedInstances
2937

core/src/main/scala/cats/data/OneAnd.scala

+6
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ final case class OneAnd[F[_], A](head: A, tail: F[A]) {
7676
def map[B](f: A => B)(implicit F: Functor[F]): OneAnd[F, B] =
7777
OneAnd(f(head), F.map(tail)(f))
7878

79+
/**
80+
* Modify the context `F` using transformation `f`.
81+
*/
82+
def mapK[G[_]](f: F ~> G): OneAnd[G, A] =
83+
OneAnd(head, f(tail))
84+
7985
/**
8086
* Typesafe equality operator.
8187
*

core/src/main/scala/cats/data/OptionT.scala

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
2828
def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] =
2929
OptionT(F.map(value)(_.map(f)))
3030

31+
/**
32+
* Modify the context `F` using transformation `f`.
33+
*/
34+
def mapK[G[_]](f: F ~> G): OptionT[G, A] = OptionT[G, A](f(value))
35+
3136
def semiflatMap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, B] =
3237
flatMap(a => OptionT.liftF(f(a)))
3338

core/src/main/scala/cats/data/Tuple2K.scala

+9-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ import cats.Contravariant
88
*
99
* See: [[https://www.cs.ox.ac.uk/jeremy.gibbons/publications/iterator.pdf The Essence of the Iterator Pattern]]
1010
*/
11-
final case class Tuple2K[F[_], G[_], A](first: F[A], second: G[A])
11+
final case class Tuple2K[F[_], G[_], A](first: F[A], second: G[A]) {
12+
13+
/**
14+
* Modify the context `G` of `second` using transformation `f`.
15+
*/
16+
def mapK[H[_]](f: G ~> H): Tuple2K[F, H, A] =
17+
Tuple2K(first, f(second))
18+
19+
}
1220

1321
object Tuple2K extends Tuple2KInstances
1422

core/src/main/scala/cats/data/WriterT.scala

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) {
2727
functorF.map(run) { z => (z._1, fn(z._2)) }
2828
}
2929

30+
/**
31+
* Modify the context `F` using transformation `f`.
32+
*/
33+
def mapK[G[_]](f: F ~> G): WriterT[G, L, V] =
34+
WriterT[G, L, V](f(run))
35+
3036
def contramap[Z](fn: Z => V)(implicit F: Contravariant[F]): WriterT[F, L, Z] =
3137
WriterT {
3238
F.contramap(run) { z => (z._1, fn(z._2)) }

free/src/main/scala/cats/free/Coyoneda.scala

+8-1
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,16 @@ sealed abstract class Coyoneda[F[_], A] extends Serializable { self =>
4646
final def map[B](f: A => B): Aux[F, B, Pivot] =
4747
unsafeApply(fi)(f.asInstanceOf[Any => Any] :: ks)
4848

49-
final def transform[G[_]](f: FunctionK[F, G]): Aux[G, A, Pivot] =
49+
/**
50+
* Modify the context `F` using transformation `f`.
51+
*/
52+
final def mapK[G[_]](f: F ~> G): Aux[G, A, Pivot] =
5053
unsafeApply(f(fi))(ks)
5154

55+
@deprecated("Use mapK", "1.0.0")
56+
final def transform[G[_]](f: FunctionK[F, G]): Aux[G, A, Pivot] =
57+
mapK(f)
58+
5259
}
5360

5461
object Coyoneda {

free/src/main/scala/cats/free/Free.scala

+25-7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ sealed abstract class Free[S[_], A] extends Product with Serializable {
1717
final def map[B](f: A => B): Free[S, B] =
1818
flatMap(a => Pure(f(a)))
1919

20+
/**
21+
* Modify the functor context `S` using transformation `f`.
22+
*
23+
* This is effectively compiling your free monad into another
24+
* language by changing the suspension functor using the given
25+
* natural transformation `f`.
26+
*
27+
* If your natural transformation is effectful, be careful. These
28+
* effects will be applied by `mapK`.
29+
*/
30+
final def mapK[T[_]](f: S ~> T): Free[T, A] =
31+
foldMap[Free[T, ?]] { // this is safe because Free is stack safe
32+
λ[FunctionK[S, Free[T, ?]]](fa => Suspend(f(fa)))
33+
}(Free.catsFreeMonadForFree)
34+
2035
/**
2136
* Bind the given continuation to the result of this computation.
2237
* All left-associated binds are reassociated to the right.
@@ -147,11 +162,8 @@ sealed abstract class Free[S[_], A] extends Product with Serializable {
147162
*
148163
* If your natural transformation is effectful, be careful. These
149164
* effects will be applied by `compile`.
150-
*/
151-
final def compile[T[_]](f: FunctionK[S, T]): Free[T, A] =
152-
foldMap[Free[T, ?]] { // this is safe because Free is stack safe
153-
λ[FunctionK[S, Free[T, ?]]](fa => Suspend(f(fa)))
154-
}(Free.catsFreeMonadForFree)
165+
*/
166+
final def compile[T[_]](f: FunctionK[S, T]): Free[T, A] = mapK(f)
155167

156168
/**
157169
* Lift into `G` (typically a `EitherK`) given `InjectK`. Analogous
@@ -169,7 +181,7 @@ sealed abstract class Free[S[_], A] extends Product with Serializable {
169181
*}}}
170182
*/
171183
final def inject[G[_]](implicit ev: InjectK[S, G]): Free[G, A] =
172-
compile(λ[S ~> G](ev.inj(_)))
184+
mapK(λ[S ~> G](ev.inj(_)))
173185

174186
override def toString: String =
175187
"Free(...)"
@@ -217,11 +229,17 @@ object Free extends FreeInstances {
217229
def defer[F[_], A](value: => Free[F, A]): Free[F, A] =
218230
pure(()).flatMap(_ => value)
219231

232+
/**
233+
* a FunctionK, suitable for composition, which calls mapK
234+
*/
235+
def mapK[F[_], G[_]](fk: FunctionK[F, G]): FunctionK[Free[F, ?], Free[G, ?]] =
236+
λ[FunctionK[Free[F, ?], Free[G, ?]]](f => f.mapK(fk))
237+
220238
/**
221239
* a FunctionK, suitable for composition, which calls compile
222240
*/
223241
def compile[F[_], G[_]](fk: FunctionK[F, G]): FunctionK[Free[F, ?], Free[G, ?]] =
224-
λ[FunctionK[Free[F, ?], Free[G, ?]]](f => f.compile(fk))
242+
mapK(fk)
225243

226244
/**
227245
* a FunctionK, suitable for composition, which calls foldMap

free/src/main/scala/cats/free/FreeT.scala

+13-8
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,27 @@ sealed abstract class FreeT[S[_], M[_], A] extends Product with Serializable {
2121
final def map[B](f: A => B)(implicit M: Applicative[M]): FreeT[S, M, B] =
2222
flatMap(a => pure(f(a)))
2323

24+
/**
25+
* Modify the context `M` using transformation `mn`.
26+
*/
27+
def mapK[N[_]](mn: M ~> N): FreeT[S, N, A] =
28+
step match {
29+
case e @ FlatMapped(_, _) =>
30+
FlatMapped(e.a.mapK(mn), e.f.andThen(_.mapK(mn)))
31+
case Suspend(m) =>
32+
Suspend(mn(m))
33+
}
34+
2435
/** Binds the given continuation to the result of this computation. */
2536
final def flatMap[B](f: A => FreeT[S, M, B]): FreeT[S, M, B] =
2637
FlatMapped(this, f)
2738

2839
/**
2940
* Changes the underlying `Monad` for this `FreeT`, ie.
3041
* turning this `FreeT[S, M, A]` into a `FreeT[S, N, A]`.
31-
*/
42+
*/
3243
def hoist[N[_]](mn: FunctionK[M, N]): FreeT[S, N, A] =
33-
step match {
34-
case e @ FlatMapped(_, _) =>
35-
FlatMapped(e.a.hoist(mn), e.f.andThen(_.hoist(mn)))
36-
case Suspend(m) =>
37-
Suspend(mn(m))
38-
}
44+
mapK(mn)
3945

4046
@deprecated("Use compile", "0.8.0")
4147
def interpret[T[_]](st: FunctionK[S, T])(implicit M: Functor[M]): FreeT[T, M, A] = compile(st)
@@ -251,4 +257,3 @@ private[free] sealed trait FreeTSemigroupK[S[_], M[_]] extends SemigroupK[FreeT[
251257
override final def combineK[A](a: FreeT[S, M, A], b: FreeT[S, M, A]): FreeT[S, M, A] =
252258
FreeT.liftT(M1.combineK(a.toM, b.toM))(M).flatMap(identity)
253259
}
254-

free/src/main/scala/cats/free/Yoneda.scala

+8-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ abstract class Yoneda[F[_], A] extends Serializable { self =>
2828
new Yoneda[F, B] {
2929
def apply[C](g: B => C): F[C] = self(f andThen g)
3030
}
31+
32+
/**
33+
* Modify the context `F` using transformation `f`.
34+
*/
35+
def mapK[G[_]](f: F ~> G): Yoneda[G, A] =
36+
new Yoneda[G, A] {
37+
def apply[B](g: A => B): G[B] = f(self(g))
38+
}
3139
}
3240

3341
object Yoneda {
@@ -48,4 +56,3 @@ object Yoneda {
4856
def apply[B](f: A => B): F[B] = F.map(fa)(f)
4957
}
5058
}
51-

free/src/test/scala/cats/free/CoyonedaSuite.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ class CoyonedaSuite extends CatsSuite {
2525
}
2626
}
2727

28-
test("transform and run is same as applying natural trans") {
28+
test("mapK and run is same as applying natural trans") {
2929
val nt = λ[FunctionK[Option, List]](_.toList)
3030
val o = Option("hello")
3131
val c = Coyoneda.lift(o)
32-
c.transform(nt).run should === (nt(o))
32+
c.mapK(nt).run should === (nt(o))
3333
}
3434

3535
test("map order") {

free/src/test/scala/cats/free/FreeTSuite.scala

+4-4
Original file line numberDiff line numberDiff line change
@@ -76,18 +76,18 @@ class FreeTSuite extends CatsSuite {
7676
Eq[FreeTOption[Unit]].eqv(expected, result) should ===(true)
7777
}
7878

79-
test("hoist to universal id equivalent to original instance") {
79+
test("mapK to universal id equivalent to original instance") {
8080
forAll { a: FreeTOption[Int] =>
81-
val b = a.hoist(FunctionK.id)
81+
val b = a.mapK(FunctionK.id)
8282
Eq[FreeTOption[Int]].eqv(a, b) should ===(true)
8383
}
8484
}
8585

86-
test("hoist stack-safety") {
86+
test("mapK stack-safety") {
8787
val a = (0 until 50000).foldLeft(Applicative[FreeTOption].pure(()))(
8888
(fu, i) => fu.flatMap(u => Applicative[FreeTOption].pure(u))
8989
)
90-
val b = a.hoist(FunctionK.id)
90+
val b = a.mapK(FunctionK.id)
9191
}
9292

9393
test("compile to universal id equivalent to original instance") {

tests/src/test/scala/cats/tests/EitherTSuite.scala

+7
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,13 @@ class EitherTSuite extends CatsSuite {
242242
}
243243
}
244244

245+
test("mapK consistent with f(value)+pure") {
246+
val f: List ~> Option = λ[List ~> Option](_.headOption)
247+
forAll { (eithert: EitherT[List, String, Int]) =>
248+
eithert.mapK(f) should === (EitherT(f(eithert.value)))
249+
}
250+
}
251+
245252
test("semiflatMap consistent with value.flatMap+f+pure") {
246253
forAll { (eithert: EitherT[List, String, Int], f: Int => List[String]) =>
247254
eithert.semiflatMap(f) should === (EitherT(eithert.value.flatMap {

tests/src/test/scala/cats/tests/IdTSuite.scala

+7
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,11 @@ class IdTSuite extends CatsSuite {
8787
}
8888
}
8989

90+
test("mapK consistent with f(value)+pure") {
91+
val f: List ~> Option = λ[List ~> Option](_.headOption)
92+
forAll { (idT: IdT[List, Int]) =>
93+
idT.mapK(f) should === (IdT(f(idT.value)))
94+
}
95+
}
96+
9097
}

0 commit comments

Comments
 (0)