From 037b30970977d69d4be2b5c244cd898f6c389f90 Mon Sep 17 00:00:00 2001 From: peterneyens Date: Wed, 30 Nov 2016 12:11:21 +0100 Subject: [PATCH] Add FunctionK and Combine two `FunctionK` to return a `Prod`. --- .../src/main/scala/cats/arrow/FunctionK.scala | 18 +++++++++++++++- .../src/main/tut/datatypes/freeapplicative.md | 14 ++----------- .../scala/cats/tests/FunctionKTests.scala | 21 ++++++++++++------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/core/src/main/scala/cats/arrow/FunctionK.scala b/core/src/main/scala/cats/arrow/FunctionK.scala index 739c019993..6657312c03 100644 --- a/core/src/main/scala/cats/arrow/FunctionK.scala +++ b/core/src/main/scala/cats/arrow/FunctionK.scala @@ -1,7 +1,7 @@ package cats package arrow -import cats.data.Coproduct +import cats.data.{Coproduct, Prod} import cats.macros.MacroCompat @@ -45,6 +45,22 @@ trait FunctionK[F[_], G[_]] extends Serializable { self => */ def or[H[_]](h: FunctionK[H, G]): FunctionK[Coproduct[F, H, ?], G] = λ[FunctionK[Coproduct[F, H, ?], G]](fa => fa.fold(self, h)) + + /** + * Composes two instances of `FunctionK` into a new `FunctionK` that transforms + * one single functor to a [[cats.data.Prod]] of two functors. + * + * {{{ + * scala> import cats.arrow.FunctionK + * scala> val list2option = λ[FunctionK[List, Option]](_.headOption) + * scala> val list2vector = λ[FunctionK[List, Vector]](_.toVector) + * scala> val optionAndVector = list2option and list2vector + * scala> optionAndVector(List(1,2,3)) + * res0: cats.data.Prod[Option,Vector,Int] = Prod(Some(1),Vector(1, 2, 3)) + * }}} + */ + def and[H[_]](h: FunctionK[F, H]): FunctionK[F, Prod[G, H, ?]] = + λ[FunctionK[F, Prod[G, H, ?]]](fa => Prod(self(fa), h(fa))) } object FunctionK { diff --git a/docs/src/main/tut/datatypes/freeapplicative.md b/docs/src/main/tut/datatypes/freeapplicative.md index c24e162a2b..f5ec9f9503 100644 --- a/docs/src/main/tut/datatypes/freeapplicative.md +++ b/docs/src/main/tut/datatypes/freeapplicative.md @@ -153,24 +153,14 @@ Another useful property `Applicative`s have over `Monad`s is that given two `App case for monads. Therefore, we can write an interpreter that uses the product of the `ParValidator` and `Log` `Applicative`s -to interpret our program in one go. +to interpret our program in one go. We can create this interpreter easily by using `FunctionK#and`. ```tut:silent import cats.data.Prod type ValidateAndLog[A] = Prod[ParValidator, Log, A] -val prodCompiler = - λ[FunctionK[ValidationOp, ValidateAndLog]] { - case Size(size) => - val f: ParValidator[Boolean] = Kleisli(str => Future { str.size >= size }) - val l: Log[Boolean] = Const(List(s"size > $size")) - Prod[ParValidator, Log, Boolean](f, l) - case HasNumber => - val f: ParValidator[Boolean] = Kleisli(str => Future(str.exists(c => "0123456789".contains(c)))) - val l: Log[Boolean] = Const(List("has number")) - Prod[ParValidator, Log, Boolean](f, l) - } +val prodCompiler: FunctionK[ValidationOp, ValidateAndLog] = parCompiler and logCompiler val prodValidation = prog.foldMap[ValidateAndLog](prodCompiler) ``` diff --git a/tests/src/test/scala/cats/tests/FunctionKTests.scala b/tests/src/test/scala/cats/tests/FunctionKTests.scala index 29c4daefc5..1d47fb9d6a 100644 --- a/tests/src/test/scala/cats/tests/FunctionKTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionKTests.scala @@ -9,10 +9,9 @@ import cats.laws.discipline.arbitrary._ class FunctionKTests extends CatsSuite { val listToOption = λ[FunctionK[List, Option]](_.headOption) - + val listToVector = λ[FunctionK[List, Vector]](_.toVector) val optionToList = λ[FunctionK[Option, List]](_.toList) - sealed trait Test1Algebra[A] { def v : A } @@ -25,11 +24,8 @@ class FunctionKTests extends CatsSuite { case class Test2[A](v : A) extends Test2Algebra[A] - val Test1NT = λ[FunctionK[Test1Algebra,Id]](_.v) - - val Test2NT = λ[FunctionK[Test2Algebra,Id]](_.v) - - type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] + val Test1FK = λ[FunctionK[Test1Algebra,Id]](_.v) + val Test2FK = λ[FunctionK[Test2Algebra,Id]](_.v) test("compose") { forAll { (list: List[Int]) => @@ -52,13 +48,22 @@ class FunctionKTests extends CatsSuite { } test("or") { - val combinedInterpreter = Test1NT or Test2NT + val combinedInterpreter = Test1FK or Test2FK forAll { (a : Int, b : Int) => combinedInterpreter(Coproduct.left(Test1(a))) should === (a) combinedInterpreter(Coproduct.right(Test2(b))) should === (b) } } + test("and") { + val combinedInterpreter = listToOption and listToVector + forAll { (list : List[Int]) => + val prod = combinedInterpreter(list) + prod.first should === (list.headOption) + prod.second should === (list.toVector) + } + } + test("lift simple unary") { def optionToList[A](option: Option[A]): List[A] = option.toList val fOptionToList = FunctionK.lift(optionToList _)