From 23a0f59cf4413302df40ea72700180b9522d3ef9 Mon Sep 17 00:00:00 2001 From: Kailuo Wang Date: Wed, 21 Sep 2016 14:25:48 -0400 Subject: [PATCH] updated tests and docs to use polymorphic lambda to create `FunctionK` --- build.sbt | 2 +- .../src/main/scala/cats/arrow/FunctionK.scala | 5 ++ core/src/main/scala/cats/data/Coproduct.scala | 5 +- docs/src/main/tut/freeapplicative.md | 49 ++++++++----------- .../test/scala/cats/free/CoyonedaTests.scala | 5 +- .../cats/free/FreeApplicativeTests.scala | 17 ++----- .../free/FreeInvariantMonoidalTests.scala | 4 +- free/src/test/scala/cats/free/FreeTests.scala | 10 ++-- .../scala/cats/tests/FunctionKTests.scala | 20 +++----- .../test/scala/cats/tests/KleisliTests.scala | 2 +- 10 files changed, 44 insertions(+), 75 deletions(-) diff --git a/build.sbt b/build.sbt index bb916341a7..291ad6fa7c 100644 --- a/build.sbt +++ b/build.sbt @@ -62,7 +62,7 @@ lazy val commonSettings = Seq( "com.github.mpilquist" %%% "simulacrum" % "0.8.0", "org.typelevel" %%% "machinist" % "0.4.1", compilerPlugin("org.scalamacros" %% "paradise" % "2.1.0" cross CrossVersion.full), - compilerPlugin("org.spire-math" %% "kind-projector" % "0.6.3") + compilerPlugin("org.spire-math" %% "kind-projector" % "0.9.0") ), fork in test := true, parallelExecution in Test := false, diff --git a/core/src/main/scala/cats/arrow/FunctionK.scala b/core/src/main/scala/cats/arrow/FunctionK.scala index d693f1942b..2ef278eb39 100644 --- a/core/src/main/scala/cats/arrow/FunctionK.scala +++ b/core/src/main/scala/cats/arrow/FunctionK.scala @@ -9,6 +9,11 @@ import cats.macros.MacroCompat * `FunctionK[F[_], G[_]]` is a functor transformation from `F` to `G` * in the same manner that function `A => B` is a morphism from values * of type `A` to `B`. + * An easy way to create a FunctionK instance is to use the Polymorphic + * lambdas provided by non/kind-projector v0.9+. E.g. + * {{{ + * val listToOption = λ[FunctionK[List, Option]](_.headOption) + * }}} */ trait FunctionK[F[_], G[_]] extends Serializable { self => diff --git a/core/src/main/scala/cats/data/Coproduct.scala b/core/src/main/scala/cats/data/Coproduct.scala index cc552b6c0d..9730e679a2 100644 --- a/core/src/main/scala/cats/data/Coproduct.scala +++ b/core/src/main/scala/cats/data/Coproduct.scala @@ -67,10 +67,7 @@ final case class Coproduct[F[_], G[_], A](run: Either[F[A], G[A]]) { * {{{ * scala> import cats.arrow.FunctionK * scala> import cats.data.Coproduct - * scala> val listToOption = - * | new FunctionK[List, Option] { - * | def apply[A](fa: List[A]): Option[A] = fa.headOption - * | } + * scala> val listToOption = λ[FunctionK[List, Option]](_.headOption) * scala> val optionToOption = FunctionK.id[Option] * scala> val cp1: Coproduct[List, Option, Int] = Coproduct.leftc(List(1,2,3)) * scala> val cp2: Coproduct[List, Option, Int] = Coproduct.rightc(Some(4)) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index 600f707598..56e51f064c 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -61,14 +61,13 @@ import cats.implicits._ type FromString[A] = String => A val compiler = - new FunctionK[ValidationOp, FromString] { - def apply[A](fa: ValidationOp[A]): String => A = + λ[FunctionK[ValidationOp, FromString]] { fa => str => fa match { case Size(size) => str.size >= size case HasNumber => str.exists(c => "0123456789".contains(c)) } - } + } ``` ```tut:book @@ -103,14 +102,13 @@ import scala.concurrent.ExecutionContext.Implicits.global type ParValidator[A] = Kleisli[Future, String, A] val parCompiler = - new FunctionK[ValidationOp, ParValidator] { - def apply[A](fa: ValidationOp[A]): ParValidator[A] = - Kleisli { str => - fa match { - case Size(size) => Future { str.size >= size } - case HasNumber => Future { str.exists(c => "0123456789".contains(c)) } - } + λ[FunctionK[ValidationOp, ParValidator]] { fa => + Kleisli { str => + fa match { + case Size(size) => Future { str.size >= size } + case HasNumber => Future { str.exists(c => "0123456789".contains(c)) } } + } } val parValidation = prog.foldMap[ParValidator](parCompiler) @@ -130,12 +128,9 @@ import cats.implicits._ type Log[A] = Const[List[String], A] val logCompiler = - new FunctionK[ValidationOp, Log] { - def apply[A](fa: ValidationOp[A]): Log[A] = - fa match { - case Size(size) => Const(List(s"size >= $size")) - case HasNumber => Const(List("has number")) - } + λ[FunctionK[ValidationOp, Log]] { + case Size(size) => Const(List(s"size >= $size")) + case HasNumber => Const(List("has number")) } def logValidation[A](validation: Validation[A]): List[String] = @@ -166,19 +161,15 @@ import cats.data.Prod type ValidateAndLog[A] = Prod[ParValidator, Log, A] val prodCompiler = - new FunctionK[ValidationOp, ValidateAndLog] { - def apply[A](fa: ValidationOp[A]): ValidateAndLog[A] = { - fa match { - 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) - } - } + λ[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 prodValidation = prog.foldMap[ValidateAndLog](prodCompiler) diff --git a/free/src/test/scala/cats/free/CoyonedaTests.scala b/free/src/test/scala/cats/free/CoyonedaTests.scala index 45e708acfa..ed6bfe2ee5 100644 --- a/free/src/test/scala/cats/free/CoyonedaTests.scala +++ b/free/src/test/scala/cats/free/CoyonedaTests.scala @@ -26,10 +26,7 @@ class CoyonedaTests extends CatsSuite { } test("transform and run is same as applying natural trans") { - val nt = - new FunctionK[Option, List] { - def apply[A](fa: Option[A]): List[A] = fa.toList - } + val nt = λ[FunctionK[Option, List]](_.toList) val o = Option("hello") val c = Coyoneda.lift(o) c.transform(nt).run should === (nt(o)) diff --git a/free/src/test/scala/cats/free/FreeApplicativeTests.scala b/free/src/test/scala/cats/free/FreeApplicativeTests.scala index 2c9039ab0c..1e6467900b 100644 --- a/free/src/test/scala/cats/free/FreeApplicativeTests.scala +++ b/free/src/test/scala/cats/free/FreeApplicativeTests.scala @@ -62,10 +62,7 @@ class FreeApplicativeTests extends CatsSuite { val f = x.map(i => (j: Int) => i + j) val r1 = y.ap(f) val r2 = r1.monad - val nt = - new FunctionK[Id, Id] { - def apply[A](fa: Id[A]): Id[A] = fa - } + val nt = FunctionK.id[Id] r1.foldMap(nt) should === (r2.foldMap(nt)) } @@ -81,9 +78,7 @@ class FreeApplicativeTests extends CatsSuite { test("FreeApplicative#analyze") { type G[A] = List[Int] - val countingNT = new FunctionK[List, G] { - def apply[A](la: List[A]): G[A] = List(la.length) - } + val countingNT = λ[FunctionK[List, G]](la => List(la.length)) val fli1 = FreeApplicative.lift[List, Int](List(1, 3, 5, 7)) fli1.analyze[G[Int]](countingNT) should === (List(4)) @@ -103,8 +98,8 @@ class FreeApplicativeTests extends CatsSuite { type Tracked[A] = State[String, A] - val f: FunctionK[Foo,Tracked] = new FunctionK[Foo,Tracked] { - def apply[A](fa: Foo[A]): Tracked[A] = State[String, A]{ s0 => + val f = λ[FunctionK[Foo,Tracked]] { fa => + State { s0 => (s0 + fa.toString + ";", fa.getA) } } @@ -126,9 +121,7 @@ class FreeApplicativeTests extends CatsSuite { val z = Apply[Dsl].map2(x, y)((_, _) => ()) - val asString: FunctionK[Id, λ[α => String]] = new FunctionK[Id, λ[α => String]] { - def apply[A](a: A): String = a.toString - } + val asString = λ[FunctionK[Id, λ[α => String]]](_.toString) z.analyze(asString) should === ("xy") } diff --git a/free/src/test/scala/cats/free/FreeInvariantMonoidalTests.scala b/free/src/test/scala/cats/free/FreeInvariantMonoidalTests.scala index 4b56509197..cbd287121c 100644 --- a/free/src/test/scala/cats/free/FreeInvariantMonoidalTests.scala +++ b/free/src/test/scala/cats/free/FreeInvariantMonoidalTests.scala @@ -59,9 +59,7 @@ class FreeInvariantMonoidalTests extends CatsSuite { test("FreeInvariantMonoidal#analyze") { type G[A] = List[Int] - val countingNT = new FunctionK[List, G] { - def apply[A](la: List[A]): G[A] = List(la.length) - } + val countingNT = λ[FunctionK[List, G]](la => List(la.length)) val fli1 = FreeInvariantMonoidal.lift[List, Int](List(1, 3, 5, 7)) fli1.analyze[G[Int]](countingNT) should === (List(4)) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 835bd75793..de77c5ed1d 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -69,10 +69,8 @@ class FreeTests extends CatsSuite { z <- if (j<10000) a(j) else Free.pure[FTestApi, Int](j) } yield z - def runner: FunctionK[FTestApi,Id] = new FunctionK[FTestApi,Id] { - def apply[A](fa: FTestApi[A]): Id[A] = fa match { - case TB(i) => i+1 - } + def runner: FunctionK[FTestApi,Id] = λ[FunctionK[FTestApi,Id]] { + case TB(i) => i+1 } assert(10000 == a(0).foldMap(runner)) @@ -117,9 +115,7 @@ object FreeTests extends FreeTestsInstances { } sealed trait FreeTestsInstances { - val headOptionU: FunctionK[List,Option] = new FunctionK[List,Option] { - def apply[A](fa: List[A]): Option[A] = fa.headOption - } + val headOptionU = λ[FunctionK[List,Option]](_.headOption) private def freeGen[F[_], A](maxDepth: Int)(implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Gen[Free[F, A]] = { val noFlatMapped = Gen.oneOf( diff --git a/tests/src/test/scala/cats/tests/FunctionKTests.scala b/tests/src/test/scala/cats/tests/FunctionKTests.scala index c2703ada3b..29c4daefc5 100644 --- a/tests/src/test/scala/cats/tests/FunctionKTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionKTests.scala @@ -7,15 +7,11 @@ import cats.data.NonEmptyList import cats.laws.discipline.arbitrary._ class FunctionKTests extends CatsSuite { - val listToOption = - new FunctionK[List, Option] { - def apply[A](fa: List[A]): Option[A] = fa.headOption - } - val optionToList = - new FunctionK[Option, List] { - def apply[A](fa: Option[A]): List[A] = fa.toList - } + val listToOption = λ[FunctionK[List, Option]](_.headOption) + + val optionToList = λ[FunctionK[Option, List]](_.toList) + sealed trait Test1Algebra[A] { def v : A @@ -29,13 +25,9 @@ class FunctionKTests extends CatsSuite { case class Test2[A](v : A) extends Test2Algebra[A] - object Test1NT extends FunctionK[Test1Algebra,Id] { - override def apply[A](fa: Test1Algebra[A]): Id[A] = fa.v - } + val Test1NT = λ[FunctionK[Test1Algebra,Id]](_.v) - object Test2NT extends FunctionK[Test2Algebra,Id] { - override def apply[A](fa: Test2Algebra[A]): Id[A] = fa.v - } + val Test2NT = λ[FunctionK[Test2Algebra,Id]](_.v) type T[A] = Coproduct[Test1Algebra, Test2Algebra, A] diff --git a/tests/src/test/scala/cats/tests/KleisliTests.scala b/tests/src/test/scala/cats/tests/KleisliTests.scala index abdb093af4..6e37fbf4bd 100644 --- a/tests/src/test/scala/cats/tests/KleisliTests.scala +++ b/tests/src/test/scala/cats/tests/KleisliTests.scala @@ -126,7 +126,7 @@ class KleisliTests extends CatsSuite { test("transform") { val opt = Kleisli { (x: Int) => Option(x.toDouble) } - val optToList = new FunctionK[Option,List] { def apply[A](fa: Option[A]): List[A] = fa.toList } + val optToList = λ[FunctionK[Option,List]](_.toList) val list = opt.transform(optToList) val is = 0.to(10).toList