Skip to content

Commit

Permalink
Add mapK to most (hopefully all) transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
andyscott committed Oct 21, 2017
1 parent 7b4814a commit 208fbd0
Show file tree
Hide file tree
Showing 15 changed files with 93 additions and 3 deletions.
1 change: 0 additions & 1 deletion core/src/main/scala/cats/data/EitherK.scala
Original file line number Diff line number Diff line change
Expand Up @@ -234,4 +234,3 @@ private[data] trait EitherKComonad[F[_], G[_]] extends Comonad[EitherK[F, G, ?]]
def extract[A](p: EitherK[F, G, A]): A =
p.extract
}

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 @@ -91,6 +91,11 @@ final case class EitherT[F[_], A, B](value: F[Either[A, B]]) {

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

/**
* Modify the context `F` using transformation `f`.
*/
def mapK[G[_]](f: F ~> G): EitherT[G, A, B] = EitherT[G, A, B](f(value))

def semiflatMap[D](f: B => F[D])(implicit F: Monad[F]): EitherT[F, A, D] =
flatMap(b => EitherT.right(f(b)))

Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/IdT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ final case class IdT[F[_], A](value: F[A]) {
def map[B](f: A => B)(implicit F: Functor[F]): IdT[F, B] =
IdT(F.map(value)(f))

/**
* Modify the context `F` using transformation `f`.
*/
def mapK[G[_]](f: F ~> G): IdT[G, A] =
IdT[G, A](f(value))

def flatMap[B](f: A => IdT[F, B])(implicit F: FlatMap[F]): IdT[F, B] =
IdT(F.flatMap(value)(f.andThen(_.value)))

Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/data/IndexedReaderWriterStateT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ final class IndexedReaderWriterStateT[F[_], E, L, SA, SB, A](val runF: F[(E, SA)
def map[B](f: A => B)(implicit F: Functor[F]): IndexedReaderWriterStateT[F, E, L, SA, SB, B] =
transform { (l, s, a) => (l, s, f(a)) }

/**
* Modify the context `F` using transformation `f`.
*/
def mapK[G[_]](f: F ~> G)(implicit F: Functor[F]): IndexedReaderWriterStateT[G, E, L, SA, SB, A] =
IndexedReaderWriterStateT.applyF(
f(F.map(runF)(rwsa => (e, sa) => f(rwsa(e, sa)))))

/**
* Modify the resulting state using `f` and the resulting value using `g`.
*/
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/data/IndexedStateT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ final class IndexedStateT[F[_], SA, SB, A](val runF: F[SA => F[(SB, A)]]) extend
def map[B](f: A => B)(implicit F: Functor[F]): IndexedStateT[F, SA, SB, B] =
transform { case (s, a) => (s, f(a)) }

/**
* Modify the context `F` using transformation `f`.
*/
def mapK[G[_]](f: F ~> G)(implicit F: Functor[F]): IndexedStateT[G, SA, SB, A] =
IndexedStateT.applyF(
f(F.map(runF)(_.andThen(fsa => f(fsa)))))

def contramap[S0](f: S0 => SA)(implicit F: Functor[F]): IndexedStateT[F, S0, SB, A] =
IndexedStateT.applyF {
F.map(runF) { safsba =>
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/data/Kleisli.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { self =>
def mapF[N[_], C](f: F[B] => N[C]): Kleisli[N, A, C] =
Kleisli(run andThen f)

/**
* Modify the context `F` using transformation `f`.
*/
def mapK[G[_]](f: F ~> G): Kleisli[G, A, B] =
Kleisli[G, A, B](run andThen f.apply)

def flatMap[C](f: B => Kleisli[F, A, C])(implicit F: FlatMap[F]): Kleisli[F, A, C] =
Kleisli((r: A) => F.flatMap[B, C](run(r))((b: B) => f(b).run(r)))

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 @@ -28,6 +28,11 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def map[B](f: A => B)(implicit F: Functor[F]): OptionT[F, B] =
OptionT(F.map(value)(_.map(f)))

/**
* Modify the context `F` using transformation `f`.
*/
def mapK[G[_]](f: F ~> G): OptionT[G, A] = OptionT[G, A](f(value))

def semiflatMap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, B] =
flatMap(a => OptionT.liftF(f(a)))

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 @@ -27,6 +27,12 @@ final case class WriterT[F[_], L, V](run: F[(L, V)]) {
functorF.map(run) { z => (z._1, fn(z._2)) }
}

/**
* Modify the context `F` using transformation `f`.
*/
def mapK[G[_]](f: F ~> G): WriterT[G, L, V] =
WriterT[G, L, V](f(run))

def contramap[Z](fn: Z => V)(implicit F: Contravariant[F]): WriterT[F, L, Z] =
WriterT {
F.contramap(run) { z => (z._1, fn(z._2)) }
Expand Down
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/EitherTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,13 @@ class EitherTSuite extends CatsSuite {
}
}

test("mapK consistent with f(value)+pure") {
val f: List ~> Option = λ[List ~> Option](_.headOption)
forAll { (eithert: EitherT[List, String, Int]) =>
eithert.mapK(f) should === (EitherT(f(eithert.value)))
}
}

test("semiflatMap consistent with value.flatMap+f+pure") {
forAll { (eithert: EitherT[List, String, Int], f: Int => List[String]) =>
eithert.semiflatMap(f) should === (EitherT(eithert.value.flatMap {
Expand Down
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/IdTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,11 @@ class IdTSuite extends CatsSuite {
}
}

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

}
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,13 @@ class ReaderWriterStateTSuite extends CatsSuite {
}
}

test("ReaderWriterStateT.mapK transforms effect") {
val f: Eval ~> Id = λ[Eval ~> Id](_.value)
forAll { (state: ReaderWriterStateT[Eval, Long, String, String, Int], env: Long, initial: String) =>
state.mapK(f).runA(env, initial) should === (state.runA(env, initial).value)
}
}

test(".get and then .run produces the same state as value") {
forAll { (c: String, initial: Long, rws: ReaderWriterState[String, String, Long, Long]) =>
val (_, state, value) = rws.get.run(c, initial).value
Expand Down
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/IndexedStateTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ class IndexedStateTSuite extends CatsSuite {
}
}

test("StateT#mapK transforms effect") {
val f: Eval ~> Id = λ[Eval ~> Id](_.value)
forAll { (state: StateT[Eval, Long, Int], initial: Long) =>
state.mapK(f).runA(initial) should === (state.runA(initial).value)
}
}

test("StateT#transformS modifies state") {
final case class Env(int: Int, str: String)
val x = StateT((x: Int) => Option((x + 1, x)))
Expand Down
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/KleisliSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ class KleisliSuite extends CatsSuite {
}
}

test("mapK") {
val t: List ~> Option = λ[List ~> Option](_.headOption)
forAll { (f: Kleisli[List, Int, Int], i: Int) =>
t(f.run(i)) should === (f.mapK(t).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))
Expand Down
7 changes: 7 additions & 0 deletions tests/src/test/scala/cats/tests/OptionTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@ class OptionTSuite extends CatsSuite {
}
}

test("mapK consistent with f(value)+pure") {
val f: List ~> Option = λ[List ~> Option](_.headOption)
forAll { (optiont: OptionT[List, Int]) =>
optiont.mapK(f) should === (OptionT(f(optiont.value)))
}
}

test("semiflatMap consistent with value.flatMap+f+pure") {
forAll { (o: OptionT[List, Int], f: Int => List[String]) =>
o.semiflatMap(f) should === (OptionT(o.value.flatMap {
Expand Down
11 changes: 9 additions & 2 deletions tests/src/test/scala/cats/tests/WriterTSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ class WriterTSuite extends CatsSuite {
WriterT.valueT[Id, Int, Int](i).value should === (i)
}
}

test("Writer.pure and WriterT.lift are consistent") {
forAll { (i: Int) =>
val writer: Writer[String, Int] = Writer.value(i)
val writerT: WriterT[Option, String, Int] = WriterT.lift(Some(i))
writer.run.some should === (writerT.run)
}
}

test("show") {
val writerT: WriterT[Id, List[String], String] = WriterT.put("foo")(List("Some log message"))
writerT.show should === ("(List(Some log message),foo)")
Expand All @@ -89,6 +89,13 @@ class WriterTSuite extends CatsSuite {
Writer.tell("foo").written should === ("foo")
}

test("mapK consistent with f(value)+pure") {
val f: List ~> Option = λ[List ~> Option](_.headOption)
forAll { (writert: WriterT[List, String, Int]) =>
writert.mapK(f) should === (WriterT(f(writert.run)))
}
}

{
// F has a SemigroupK
implicit val F: SemigroupK[ListWrapper] = ListWrapper.semigroupK
Expand Down

0 comments on commit 208fbd0

Please sign in to comment.