From 427a7c830b642587d93b4469e16c57181c911de3 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 20 Nov 2015 07:39:04 -0500 Subject: [PATCH 01/39] Comment out unused XorTMonadCombine This trait is currently unused, because the implicit def that uses it is commented out until we have separated weak/strong laws for `MonadCombine`. We probably shouldn't expose it if it doesn't satisfy our current `MonadCombine` laws. --- core/src/main/scala/cats/data/XorT.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 5bc5669bbe9..a6666afe47e 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -277,10 +277,12 @@ private[data] trait XorTMonadFilter[F[_], L] extends MonadFilter[XorT[F, L, ?]] def empty[A]: XorT[F, L, A] = XorT(F.pure(Xor.left(L.empty))) } +/* TODO violates right absorbtion, right distributivity, and left distributivity -- re-enable when MonadCombine laws are split in to weak/strong private[data] trait XorTMonadCombine[F[_], L] extends MonadCombine[XorT[F, L, ?]] with XorTMonadFilter[F, L] with XorTSemigroupK[F, L] { implicit val F: Monad[F] implicit val L: Monoid[L] } +*/ private[data] sealed trait XorTFoldable[F[_], L] extends Foldable[XorT[F, L, ?]] { implicit def F0: Foldable[F] From 99e47064b1b7e12bca3ba56653396f289ed83ca1 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 20 Nov 2015 08:06:03 -0500 Subject: [PATCH 02/39] Add some StreamingT tests --- .../test/scala/cats/tests/ListWrapper.scala | 10 +++-- .../scala/cats/tests/StreamingTTests.scala | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 35f81775704..a6ed3c9d40f 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -45,7 +45,7 @@ object ListWrapper { def eqv[A : Eq]: Eq[ListWrapper[A]] = Eq[List[A]].on[ListWrapper[A]](_.list) - def foldable: Foldable[ListWrapper] = + val foldable: Foldable[ListWrapper] = new Foldable[ListWrapper] { def foldLeft[A, B](fa: ListWrapper[A], b: B)(f: (B, A) => B): B = Foldable[List].foldLeft(fa.list, b)(f) @@ -54,13 +54,13 @@ object ListWrapper { Foldable[List].foldRight(fa.list, lb)(f) } - def functor: Functor[ListWrapper] = + val functor: Functor[ListWrapper] = new Functor[ListWrapper] { def map[A, B](fa: ListWrapper[A])(f: A => B): ListWrapper[B] = ListWrapper(Functor[List].map(fa.list)(f)) } - def semigroupK: SemigroupK[ListWrapper] = + val semigroupK: SemigroupK[ListWrapper] = new SemigroupK[ListWrapper] { def combine[A](x: ListWrapper[A], y: ListWrapper[A]): ListWrapper[A] = ListWrapper(SemigroupK[List].combine(x.list, y.list)) @@ -68,7 +68,7 @@ object ListWrapper { def semigroup[A]: Semigroup[ListWrapper[A]] = semigroupK.algebra[A] - def monadCombine: MonadCombine[ListWrapper] = { + val monadCombine: MonadCombine[ListWrapper] = { val M = MonadCombine[List] new MonadCombine[ListWrapper] { @@ -84,6 +84,8 @@ object ListWrapper { } } + val monad: Monad[ListWrapper] = monadCombine + def monoid[A]: Monoid[ListWrapper[A]] = monadCombine.algebra[A] implicit def listWrapperArbitrary[A: Arbitrary]: Arbitrary[ListWrapper[A]] = diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 61942b75bd3..5e897202fbe 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -25,6 +25,21 @@ class StreamingTTests extends CatsSuite { checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) + { + implicit val F = ListWrapper.monad + implicit val O = ListWrapper.partialOrder[List[Int]] + checkAll("StreamingT[ListWrapper, Int]", OrderLaws[StreamingT[ListWrapper, Int]].partialOrder) + checkAll("PartialOrder[StreamingT[ListWrapper, Int]]", SerializableTests.serializable(PartialOrder[StreamingT[ListWrapper, Int]])) + } + + { + implicit val F = ListWrapper.monad + implicit val E = ListWrapper.eqv[List[Int]] + checkAll("StreamingT[ListWrapper, Int]", OrderLaws[StreamingT[ListWrapper, Int]].eqv) + checkAll("Eq[StreamingT[ListWrapper, Int]]", SerializableTests.serializable(Eq[StreamingT[ListWrapper, Int]])) + } + + test("uncons with Id consistent with List headOption/tail") { forAll { (s: StreamingT[Id, Int]) => val sList = s.toList @@ -131,6 +146,36 @@ class StreamingTTests extends CatsSuite { s.drop(i).toList should === (s.toList.drop(i)) } } + + test("fromVector") { + forAll { (xs: Vector[Int]) => + StreamingT.fromVector[Id, Int](xs).toList.toVector should === (xs) + } + } + + test("fromList") { + forAll { (xs: List[Int]) => + StreamingT.fromList[Id, Int](xs).toList should === (xs) + } + } + + test("single consistent with apply") { + forAll { (i: Int) => + StreamingT[Id, Int](i) should === (StreamingT.single[Id, Int](i)) + } + } + + test("var-arg apply") { + forAll { (x1: Int, x2: Int, x3: Int, x4: Int) => + val fromList = StreamingT.fromList[Id, Int](x1 :: x2 :: x3 :: x4 :: Nil) + StreamingT[Id, Int](x1, x2, x3, x4) should === (fromList) + } + + forAll { (x1: Int, x2: Int, tail: List[Int]) => + val fromList = StreamingT.fromList[Id, Int](x1 :: x2 :: tail) + StreamingT[Id, Int](x1, x2, tail: _*) should === (fromList) + } + } } class SpecificStreamingTTests extends CatsSuite { From bcf5aa6e0d98d883003dc992539353a158eaf01b Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 14:59:29 +0000 Subject: [PATCH 03/39] add Function1 Semigroup instance --- core/src/main/scala/cats/std/function.scala | 30 +++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 85e33b4ca45..9f255da5844 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -26,7 +26,7 @@ trait Function0Instances { } } -trait Function1Instances { +trait Function1Instances extends Function1Instances0 { implicit def function1Contravariant[R]: Contravariant[? => R] = new Contravariant[? => R] { def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R = @@ -71,13 +71,27 @@ trait Function1Instances { def compose[A, B, C](f: B => C, g: A => B): A => C = f.compose(g) } - implicit def function1Monoid[A,B](implicit B: Monoid[B]): Monoid[A => B] = - new Monoid[A => B] { - def empty: A => B = _ => B.empty - def combine(x: A => B, y: A => B): A => B = { a => - B.combine(x(a), y(a)) - } - } + implicit def function1Monoid[A,B](implicit M: Monoid[B]): Monoid[A => B] = + new Function1Monoid[A, B] { def B: Monoid[B] = M } +} + +trait Function1Instances0 { + implicit def function1Semigroup[A,B](implicit S: Semigroup[B]): Semigroup[A => B] = + new Function1Semigroup[A, B] { def B: Semigroup[B] = S } +} + +private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { + implicit def B: Monoid[B] + + override def empty: A => B = _ => B.empty +} + +private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { + implicit def B: Semigroup[B] + + override def combine(x: A => B, y: A => B): A => B = { a => + B.combine(x(a), y(a)) + } } trait FunctionInstances From c5572876456cc6b88fde8501f90ec1b6c6f9363c Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 15:51:28 +0000 Subject: [PATCH 04/39] * add MonoidK and SemigroupK instances for Function1 --- core/src/main/scala/cats/std/function.scala | 24 +++++++++++++++---- .../test/scala/cats/tests/FunctionTests.scala | 7 ++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 9f255da5844..4538a0da0e0 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -73,17 +73,17 @@ trait Function1Instances extends Function1Instances0 { implicit def function1Monoid[A,B](implicit M: Monoid[B]): Monoid[A => B] = new Function1Monoid[A, B] { def B: Monoid[B] = M } + + implicit val function1MonoidK: MonoidK[Lambda[A => A => A]] = + new Function1MonoidK {} } trait Function1Instances0 { implicit def function1Semigroup[A,B](implicit S: Semigroup[B]): Semigroup[A => B] = new Function1Semigroup[A, B] { def B: Semigroup[B] = S } -} - -private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { - implicit def B: Monoid[B] - override def empty: A => B = _ => B.empty + implicit val function1SemigroupK: SemigroupK[Lambda[A => A => A]] = + new Function1SemigroupK {} } private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { @@ -94,6 +94,20 @@ private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { } } +private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { + implicit def B: Monoid[B] + + override def empty: A => B = _ => B.empty +} + +private[std] trait Function1SemigroupK extends SemigroupK[Lambda[A => A => A]] { + override def combine[A](x: A => A, y: A => A): A => A = x compose y +} + +private[std] trait Function1MonoidK extends MonoidK[Lambda[A => A => A]] with Function1SemigroupK { + override def empty[A]: A => A = identity[A] +} + trait FunctionInstances extends Function0Instances with Function1Instances diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 40191412f5b..17d12a1adc4 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -8,6 +8,7 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ +import cats.std.function.{ function1MonoidK, function1SemigroupK } import algebra.laws.GroupLaws class FunctionTests extends CatsSuite { @@ -28,4 +29,10 @@ class FunctionTests extends CatsSuite { checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) + + checkAll("Function1[Int, Int]", MonoidKTests[Lambda[A => A => A]].semigroupK[Int]) + checkAll("SemigroupK[Lambda[A => A => A]", SerializableTests.serializable(function1SemigroupK)) + + checkAll("Function1[Int, Int]", MonoidKTests[Lambda[A => A => A]].monoidK[Int]) + checkAll("MonoidK[Lambda[A => A => A]", SerializableTests.serializable(function1MonoidK)) } From 16fdaae5d1d3605977aa8455cae427a9baea8249 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Sat, 21 Nov 2015 10:53:37 -0500 Subject: [PATCH 05/39] Pass along error when serializable tests fail This is the same work that was done in algebra in [#117](/~https://github.com/non/algebra/pull/117). --- laws/src/main/scala/cats/laws/SerializableLaws.scala | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/laws/src/main/scala/cats/laws/SerializableLaws.scala b/laws/src/main/scala/cats/laws/SerializableLaws.scala index 7d505c97152..38d3284fc89 100644 --- a/laws/src/main/scala/cats/laws/SerializableLaws.scala +++ b/laws/src/main/scala/cats/laws/SerializableLaws.scala @@ -2,10 +2,12 @@ package cats package laws import org.scalacheck.Prop -import org.scalacheck.Prop.{ False, Proof, Result } +import org.scalacheck.Prop.{ Exception, False, Proof, Result } import catalysts.Platform +import scala.util.control.NonFatal + /** * Check for Java Serializability. * @@ -42,8 +44,8 @@ object SerializableLaws { val a2 = ois.readObject() ois.close() Result(status = Proof) - } catch { case _: Throwable => - Result(status = False) + } catch { case NonFatal(t) => + Result(status = Exception(t)) } finally { oos.close() if (ois != null) ois.close() From 512fb73f2f0fdc7e8fb85a7f4f80ce1d05ef67df Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 15:55:50 +0000 Subject: [PATCH 06/39] * add Semigroup tests for Function1 --- tests/src/test/scala/cats/tests/FunctionTests.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 17d12a1adc4..a0815dd79e5 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -28,6 +28,8 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) + checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].semigroup) + checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) checkAll("Function1[Int, Int]", MonoidKTests[Lambda[A => A => A]].semigroupK[Int]) From 0a1f1f719eecdf8719085b656415bc31c4e49a5e Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sat, 21 Nov 2015 16:57:31 +0000 Subject: [PATCH 07/39] * ensure that Function1 semigroup instance is tested properly. --- tests/src/test/scala/cats/tests/FunctionTests.scala | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index a0815dd79e5..9a4af0c2681 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -8,7 +8,6 @@ import cats.functor.Contravariant import cats.laws.discipline._ import cats.laws.discipline.eq._ import cats.laws.discipline.arbitrary._ -import cats.std.function.{ function1MonoidK, function1SemigroupK } import algebra.laws.GroupLaws class FunctionTests extends CatsSuite { @@ -28,7 +27,7 @@ class FunctionTests extends CatsSuite { checkAll("Function1[Int, Int]", ContravariantTests[? => Int].contravariant[Int, Int, Int]) checkAll("Contravariant[? => Int]", SerializableTests.serializable(Contravariant[? => Int])) - checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].semigroup) + checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].semigroup(function1Semigroup[String, Int])) checkAll("Function1[String, Int]", GroupLaws[Function1[String, Int]].monoid) From 9714421540b9db5503895109c65d7edb44fba36a Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 21 Nov 2015 20:22:16 +0000 Subject: [PATCH 08/39] Adds additional tests for Option T * Tests consistency between flatMap and flatMapF * Tests consistency between Show[OptionT] instance and OptionT.show --- .../src/test/scala/cats/tests/OptionTTests.scala | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index ca7cd405d10..526d4874de3 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -1,6 +1,6 @@ package cats.tests -import cats.{Applicative, Id, Monad} +import cats.{Applicative, Id, Monad, Show} import cats.data.{OptionT, Validated, Xor} import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadCombineTests, SerializableTests} import cats.laws.discipline.arbitrary._ @@ -81,6 +81,12 @@ class OptionTTests extends CatsSuite { } } + test("flatMap and flatMapF consistent") { + forAll { (optionT: OptionT[List, Int], f: Int => OptionT[List, Int]) => + optionT.flatMap(f) should === (optionT.flatMapF(f(_).value)) + } + } + test("OptionT[Id, A].toRight consistent with Xor.fromOption") { forAll { (o: OptionT[Id, Int], s: String) => o.toRight(s).value should === (Xor.fromOption(o.value, s)) @@ -117,11 +123,17 @@ class OptionTTests extends CatsSuite { } } - test("show"){ + test("show") { val xor: String Xor Option[Int] = Xor.right(Some(1)) OptionT[Xor[String, ?], Int](xor).show should === ("Xor.Right(Some(1))") } + test("implicit Show[OptionT] instance and explicit show method are consistent") { + forAll { optionT: OptionT[List, Int] => + optionT.show should === (implicitly[Show[OptionT[List, Int]]].show(optionT)) + } + } + test("transform consistent with value.map") { forAll { (o: OptionT[List, Int], f: Option[Int] => Option[String]) => o.transform(f) should === (OptionT(o.value.map(f))) From 4a922ab9e117c43a4791813d2f4391046e8bfb6d Mon Sep 17 00:00:00 2001 From: Luis Sanchez Date: Sun, 22 Nov 2015 17:21:49 +0000 Subject: [PATCH 09/39] * make traits private and sealed to be consistent with #612 --- core/src/main/scala/cats/std/function.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/cats/std/function.scala b/core/src/main/scala/cats/std/function.scala index 4538a0da0e0..8d7c3af2d7b 100644 --- a/core/src/main/scala/cats/std/function.scala +++ b/core/src/main/scala/cats/std/function.scala @@ -6,7 +6,7 @@ import cats.arrow.{Arrow, Choice} import cats.data.Xor import cats.functor.Contravariant -trait Function0Instances { +private[std] sealed trait Function0Instances { implicit val function0Instance: Bimonad[Function0] = new Bimonad[Function0] { def extract[A](x: () => A): A = x() @@ -26,7 +26,7 @@ trait Function0Instances { } } -trait Function1Instances extends Function1Instances0 { +private[std] sealed trait Function1Instances extends Function1Instances0 { implicit def function1Contravariant[R]: Contravariant[? => R] = new Contravariant[? => R] { def contramap[T1, T0](fa: T1 => R)(f: T0 => T1): T0 => R = @@ -78,7 +78,7 @@ trait Function1Instances extends Function1Instances0 { new Function1MonoidK {} } -trait Function1Instances0 { +private[std] sealed trait Function1Instances0 { implicit def function1Semigroup[A,B](implicit S: Semigroup[B]): Semigroup[A => B] = new Function1Semigroup[A, B] { def B: Semigroup[B] = S } @@ -86,7 +86,7 @@ trait Function1Instances0 { new Function1SemigroupK {} } -private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { +private[std] sealed trait Function1Semigroup[A, B] extends Semigroup[A => B] { implicit def B: Semigroup[B] override def combine(x: A => B, y: A => B): A => B = { a => @@ -94,17 +94,17 @@ private[std] trait Function1Semigroup[A, B] extends Semigroup[A => B] { } } -private[std] trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { +private[std] sealed trait Function1Monoid[A, B] extends Monoid[A => B] with Function1Semigroup[A, B] { implicit def B: Monoid[B] override def empty: A => B = _ => B.empty } -private[std] trait Function1SemigroupK extends SemigroupK[Lambda[A => A => A]] { +private[std] sealed trait Function1SemigroupK extends SemigroupK[Lambda[A => A => A]] { override def combine[A](x: A => A, y: A => A): A => A = x compose y } -private[std] trait Function1MonoidK extends MonoidK[Lambda[A => A => A]] with Function1SemigroupK { +private[std] sealed trait Function1MonoidK extends MonoidK[Lambda[A => A => A]] with Function1SemigroupK { override def empty[A]: A => A = identity[A] } From 355db32968e5fd0b01673d8e092fb437cf7cd98b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 23 Nov 2015 08:32:29 -0500 Subject: [PATCH 10/39] Add more WriterT instances * SemigroupK * MonoidK * Alternative * MonadFilter * MonadCombine --- core/src/main/scala/cats/data/WriterT.scala | 73 +++++++++++++++++-- .../test/scala/cats/tests/ListWrapper.scala | 6 ++ .../test/scala/cats/tests/WriterTTests.scala | 70 +++++++++++++++++- 3 files changed, 139 insertions(+), 10 deletions(-) diff --git a/core/src/main/scala/cats/data/WriterT.scala b/core/src/main/scala/cats/data/WriterT.scala index 69dbc5f0792..304f2796c75 100644 --- a/core/src/main/scala/cats/data/WriterT.scala +++ b/core/src/main/scala/cats/data/WriterT.scala @@ -65,9 +65,9 @@ private[data] sealed abstract class WriterTInstances extends WriterTInstances0 { } private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 { - implicit def writerTMonad[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] = - new WriterTMonad[F, L] { - implicit val F0: Monad[F] = F + implicit def writerTMonadCombine[F[_], L](implicit F: MonadCombine[F], L: Monoid[L]): MonadCombine[WriterT[F, L, ?]] = + new WriterTMonadCombine[F, L] { + implicit val F0: MonadCombine[F] = F implicit val L0: Monoid[L] = L } @@ -82,21 +82,55 @@ private[data] sealed abstract class WriterTInstances0 extends WriterTInstances1 } private[data] sealed abstract class WriterTInstances1 extends WriterTInstances2 { + implicit def writerTMonadFilter[F[_], L](implicit F: MonadFilter[F], L: Monoid[L]): MonadFilter[WriterT[F, L, ?]] = + new WriterTMonadFilter[F, L] { + implicit val F0: MonadFilter[F] = F + implicit val L0: Monoid[L] = L + } +} +private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { + implicit def writerTMonad[F[_], L](implicit F: Monad[F], L: Monoid[L]): Monad[WriterT[F, L, ?]] = + new WriterTMonad[F, L] { + implicit val F0: Monad[F] = F + implicit val L0: Monoid[L] = L + } +} + +private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { + implicit def writerTAlternative[F[_], L](implicit F: Alternative[F], L: Monoid[L]): Alternative[WriterT[F, L, ?]] = + new WriterTAlternative[F, L] { + implicit val F0: Alternative[F] = F + implicit val L0: Monoid[L] = L + } +} + +private[data] sealed abstract class WriterTInstances4 extends WriterTInstances5 { implicit def writerTApplicative[F[_], L](implicit F: Applicative[F], L: Monoid[L]): Applicative[WriterT[F, L, ?]] = new WriterTApplicative[F, L] { implicit val F0: Applicative[F] = F implicit val L0: Monoid[L] = L } + + implicit def writerTMonoidK[F[_], L](implicit F: MonoidK[F]): MonoidK[WriterT[F, L, ?]] = + new WriterTMonoidK[F, L] { + implicit val F0: MonoidK[F] = F + } } -private[data] sealed abstract class WriterTInstances2 extends WriterTInstances3 { + +private[data] sealed abstract class WriterTInstances5 extends WriterTInstances6 { implicit def writerTFlatMap[F[_], L](implicit F: FlatMap[F], L: Semigroup[L]): FlatMap[WriterT[F, L, ?]] = new WriterTFlatMap[F, L] { implicit val F0: FlatMap[F] = F implicit val L0: Semigroup[L] = L } + + implicit def writerTSemigroupK[F[_], L](implicit F: SemigroupK[F]): SemigroupK[WriterT[F, L, ?]] = + new WriterTSemigroupK[F, L] { + implicit val F0: SemigroupK[F] = F + } } -private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 { +private[data] sealed abstract class WriterTInstances6 extends WriterTInstances7 { implicit def writerTApply[F[_], L](implicit F: Apply[F], L: Semigroup[L]): Apply[WriterT[F, L, ?]] = new WriterTApply[F, L] { implicit val F0: Apply[F] = F @@ -104,7 +138,7 @@ private[data] sealed abstract class WriterTInstances3 extends WriterTInstances4 } } -private[data] sealed abstract class WriterTInstances4 { +private[data] sealed abstract class WriterTInstances7 { implicit def writerTFunctor[F[_], L](implicit F: Functor[F]): Functor[WriterT[F, L, ?]] = new WriterTFunctor[F, L] { implicit val F0: Functor[F] = F } @@ -149,6 +183,33 @@ private[data] sealed trait WriterTMonad[F[_], L] extends WriterTApplicative[F, L fa.flatMap(f) } +private[data] sealed trait WriterTSemigroupK[F[_], L] extends SemigroupK[WriterT[F, L, ?]] { + implicit def F0: SemigroupK[F] + + def combine[A](x: WriterT[F, L, A], y: WriterT[F, L, A]): WriterT[F, L, A] = + WriterT(F0.combine(x.run, y.run)) +} + +private[data] sealed trait WriterTMonoidK[F[_], L] extends MonoidK[WriterT[F, L, ?]] with WriterTSemigroupK[F, L] { + override implicit def F0: MonoidK[F] + + def empty[A]: WriterT[F, L, A] = WriterT(F0.empty) +} + +private[data] sealed trait WriterTAlternative[F[_], L] extends Alternative[WriterT[F, L, ?]] with WriterTMonoidK[F, L] with WriterTApplicative[F, L] { + override implicit def F0: Alternative[F] +} + +private[data] sealed trait WriterTMonadFilter[F[_], L] extends MonadFilter[WriterT[F, L, ?]] with WriterTMonad[F, L] { + override implicit def F0: MonadFilter[F] + + def empty[A]: WriterT[F, L, A] = WriterT(F0.empty) +} + +private[data] sealed trait WriterTMonadCombine[F[_], L] extends MonadCombine[WriterT[F, L, ?]] with WriterTMonad[F, L] with WriterTAlternative[F, L] { + override implicit def F0: MonadCombine[F] +} + trait WriterTFunctions { def putT[F[_], L, V](vf: F[V])(l: L)(implicit functorF: Functor[F]): WriterT[F, L, V] = WriterT(functorF.map(vf)(v => (l, v))) diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 35f81775704..1940880eba4 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -84,6 +84,12 @@ object ListWrapper { } } + def monoidK: MonoidK[ListWrapper] = monadCombine + + def monadFilter: MonadFilter[ListWrapper] = monadCombine + + def alternative: Alternative[ListWrapper] = monadCombine + def monoid[A]: Monoid[ListWrapper[A]] = monadCombine.algebra[A] implicit def listWrapperArbitrary[A: Arbitrary]: Arbitrary[ListWrapper[A]] = diff --git a/tests/src/test/scala/cats/tests/WriterTTests.scala b/tests/src/test/scala/cats/tests/WriterTTests.scala index 9bc21198f47..b3fb61e2160 100644 --- a/tests/src/test/scala/cats/tests/WriterTTests.scala +++ b/tests/src/test/scala/cats/tests/WriterTTests.scala @@ -44,6 +44,24 @@ class WriterTTests extends CatsSuite { } } + { + // F has a SemigroupK + implicit val F: SemigroupK[ListWrapper] = ListWrapper.semigroupK + + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", SemigroupKTests[WriterT[ListWrapper, ListWrapper[Int], ?]].semigroupK[Int]) + checkAll("SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + + { + // F has a MonoidK + implicit val F: MonoidK[ListWrapper] = ListWrapper.monoidK + + SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]] + + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonoidKTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monoidK[Int]) + checkAll("MonoidK[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(MonoidK[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + { // F has a Functor and L has no Semigroup implicit val F: Functor[ListWrapper] = ListWrapper.functor @@ -69,7 +87,7 @@ class WriterTTests extends CatsSuite { { // F has an Apply and L has a Semigroup implicit val F: Apply[ListWrapper] = ListWrapper.monadCombine - implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroup[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", ApplyTests[WriterT[ListWrapper, ListWrapper[Int], ?]].apply[Int, Int, Int]) @@ -88,7 +106,7 @@ class WriterTTests extends CatsSuite { { // F has a FlatMap and L has a Semigroup implicit val F: FlatMap[ListWrapper] = ListWrapper.monadCombine - implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroupK.algebra[Int] + implicit val L: Semigroup[ListWrapper[Int]] = ListWrapper.semigroup[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] @@ -111,7 +129,7 @@ class WriterTTests extends CatsSuite { { // F has an Applicative and L has a Monoid implicit val F: Applicative[ListWrapper] = ListWrapper.monadCombine - implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] @@ -134,7 +152,7 @@ class WriterTTests extends CatsSuite { { // F has a Monad and L has a Monoid implicit val F: Monad[ListWrapper] = ListWrapper.monadCombine - implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monadCombine.algebra[Int] + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] @@ -161,4 +179,48 @@ class WriterTTests extends CatsSuite { FlatMap[Logged] Monad[Logged] } + + { + // F has an Alternative and L has a Monoid + implicit val F: Alternative[ListWrapper] = ListWrapper.alternative + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", AlternativeTests[WriterT[ListWrapper, ListWrapper[Int], ?]].alternative[Int, Int, Int]) + checkAll("Alternative[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(Alternative[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + + { + // F has a MonadFilter and L has a Monoid + implicit val F: MonadFilter[ListWrapper] = ListWrapper.monadFilter + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]] + Monad[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonadFilterTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monadFilter[Int, Int, Int]) + checkAll("MonadFilter[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(MonadFilter[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } + + { + // F has a MonadCombine and L has a Monoid + implicit val F: MonadCombine[ListWrapper] = ListWrapper.monadCombine + implicit val L: Monoid[ListWrapper[Int]] = ListWrapper.monoid[Int] + + Functor[WriterT[ListWrapper, ListWrapper[Int], ?]] + Apply[WriterT[ListWrapper, ListWrapper[Int], ?]] + Applicative[WriterT[ListWrapper, ListWrapper[Int], ?]] + FlatMap[WriterT[ListWrapper, ListWrapper[Int], ?]] + Monad[WriterT[ListWrapper, ListWrapper[Int], ?]] + MonadFilter[WriterT[ListWrapper, ListWrapper[Int], ?]] + Alternative[WriterT[ListWrapper, ListWrapper[Int], ?]] + SemigroupK[WriterT[ListWrapper, ListWrapper[Int], ?]] + MonoidK[WriterT[ListWrapper, ListWrapper[Int], ?]] + checkAll("WriterT[ListWrapper, ListWrapper[Int], ?]", MonadCombineTests[WriterT[ListWrapper, ListWrapper[Int], ?]].monadCombine[Int, Int, Int]) + checkAll("MonadCombine[WriterT[ListWrapper, ListWrapper[Int], ?]]", SerializableTests.serializable(MonadCombine[WriterT[ListWrapper, ListWrapper[Int], ?]])) + } } From 2ee0a8a9d3ada7d19ab6fec746bd52e3e614cb2f Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 23 Nov 2015 09:28:15 -0500 Subject: [PATCH 11/39] Move NaturalTransformation.or from companion to trait I think this provides slightly nicer syntax for combining natural transformations, and it makes it more consistent with `andThen` and `compose`. If people like it, they can merge, but if not it's no problem. --- .../scala/cats/arrow/NaturalTransformation.scala | 16 ++++++++-------- docs/src/main/tut/freemonad.md | 2 +- .../cats/tests/NaturalTransformationTests.scala | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/arrow/NaturalTransformation.scala b/core/src/main/scala/cats/arrow/NaturalTransformation.scala index 336fc92a106..87a96e92fcd 100644 --- a/core/src/main/scala/cats/arrow/NaturalTransformation.scala +++ b/core/src/main/scala/cats/arrow/NaturalTransformation.scala @@ -13,6 +13,14 @@ trait NaturalTransformation[F[_], G[_]] extends Serializable { self => def andThen[H[_]](f: NaturalTransformation[G, H]): NaturalTransformation[F, H] = f.compose(self) + + def or[H[_]](h: H ~> G): Coproduct[F, H, ?] ~> G = + new (Coproduct[F, H, ?] ~> G) { + def apply[A](fa: Coproduct[F, H, A]): G[A] = fa.run match { + case Xor.Left(ff) => self(ff) + case Xor.Right(gg) => h(gg) + } + } } object NaturalTransformation { @@ -20,12 +28,4 @@ object NaturalTransformation { new NaturalTransformation[F, F] { def apply[A](fa: F[A]): F[A] = fa } - - def or[F[_], G[_], H[_]](f: F ~> H, g: G ~> H): Coproduct[F, G, ?] ~> H = - new (Coproduct[F, G, ?] ~> H) { - def apply[A](fa: Coproduct[F, G, A]): H[A] = fa.run match { - case Xor.Left(ff) => f(ff) - case Xor.Right(gg) => g(gg) - } - } } diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 46e95b4d657..57551b2fe0c 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -448,7 +448,7 @@ object InMemoryDatasourceInterpreter extends (DataOp ~> Id) { } } -val interpreter: CatsApp ~> Id = NaturalTransformation.or(InMemoryDatasourceInterpreter, ConsoleCatsInterpreter) +val interpreter: CatsApp ~> Id = InMemoryDatasourceInterpreter or ConsoleCatsInterpreter ``` Now if we run our program and type in "snuggles" when prompted, we see something like this: diff --git a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala index 9669edbb753..4a70916c919 100644 --- a/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala +++ b/tests/src/test/scala/cats/tests/NaturalTransformationTests.scala @@ -59,10 +59,10 @@ class NaturalTransformationTests extends CatsSuite { } test("or") { - val combinedInterpreter = NaturalTransformation.or(Test1NT, Test2NT) + val combinedInterpreter = Test1NT or Test2NT forAll { (a : Int, b : Int) => - (combinedInterpreter(Coproduct.left(Test1(a))) == Id.pure(a)) should ===(true) - (combinedInterpreter(Coproduct.right(Test2(b))) == Id.pure(b)) should ===(true) + combinedInterpreter(Coproduct.left(Test1(a))) should === (Id.pure(a)) + combinedInterpreter(Coproduct.right(Test2(b))) should === (Id.pure(b)) } } } From f496356789fdea67e6248a07a9dd7974141332e3 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 23 Nov 2015 09:41:03 -0500 Subject: [PATCH 12/39] Fix compile errors due to NaturalTransformation.or move --- docs/src/main/tut/freemonad.md | 1 - free/src/test/scala/cats/free/InjectTests.scala | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/src/main/tut/freemonad.md b/docs/src/main/tut/freemonad.md index 57551b2fe0c..ad85d63e4d6 100644 --- a/docs/src/main/tut/freemonad.md +++ b/docs/src/main/tut/freemonad.md @@ -357,7 +357,6 @@ lets us compose different algebras in the context of `Free`. Let's see a trivial example of unrelated ADT's getting composed as a `Coproduct` that can form a more complex program. ```tut:silent -import cats.arrow.NaturalTransformation import cats.data.{Xor, Coproduct} import cats.free.{Inject, Free} import cats.{Id, ~>} diff --git a/free/src/test/scala/cats/free/InjectTests.scala b/free/src/test/scala/cats/free/InjectTests.scala index 7a83165beec..0bd4ff45d01 100644 --- a/free/src/test/scala/cats/free/InjectTests.scala +++ b/free/src/test/scala/cats/free/InjectTests.scala @@ -1,7 +1,6 @@ package cats package free -import cats.arrow.NaturalTransformation import cats.data.{Xor, Coproduct} import cats.laws.discipline.arbitrary import cats.tests.CatsSuite @@ -53,7 +52,7 @@ class InjectTests extends CatsSuite { } } - val coProductInterpreter: T ~> Id = NaturalTransformation.or(Test1Interpreter, Test2Interpreter) + val coProductInterpreter: T ~> Id = Test1Interpreter or Test2Interpreter val x: Free[T, Int] = Free.inject[Test1Algebra, T](Test1(1, identity)) From a22676c28bc9cb20f54c9f1408e9e0214c0695fd Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Tue, 24 Nov 2015 19:42:56 +0000 Subject: [PATCH 13/39] Reduce OptionT from MonadCombine to Monad Reduces OptionT to Monad from MonadCombine. MonadCombine for OptionT was not tested and does not pass laws. --- core/src/main/scala/cats/data/OptionT.scala | 9 ++++----- tests/src/test/scala/cats/tests/OptionTTests.scala | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 913a6a55955..b683b571085 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -137,8 +137,9 @@ private[data] sealed trait OptionTInstances1 { } private[data] sealed trait OptionTInstances extends OptionTInstances1 { - implicit def optionTMonadCombine[F[_]](implicit F: Monad[F]): MonadCombine[OptionT[F, ?]] = - new MonadCombine[OptionT[F, ?]] { + + implicit def optionTMonad[F[_]](implicit F: Monad[F]): Monad[OptionT[F, ?]] = + new Monad[OptionT[F, ?]] { def pure[A](a: A): OptionT[F, A] = OptionT.pure(a) def flatMap[A, B](fa: OptionT[F, A])(f: A => OptionT[F, B]): OptionT[F, B] = @@ -146,10 +147,8 @@ private[data] sealed trait OptionTInstances extends OptionTInstances1 { override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f) - - override def empty[A]: OptionT[F,A] = OptionT(F.pure(None)) - override def combine[A](x: OptionT[F,A], y: OptionT[F,A]): OptionT[F,A] = x orElse y } + implicit def optionTEq[F[_], A](implicit FA: Eq[F[Option[A]]]): Eq[OptionT[F, A]] = FA.on(_.value) diff --git a/tests/src/test/scala/cats/tests/OptionTTests.scala b/tests/src/test/scala/cats/tests/OptionTTests.scala index 526d4874de3..84521677929 100644 --- a/tests/src/test/scala/cats/tests/OptionTTests.scala +++ b/tests/src/test/scala/cats/tests/OptionTTests.scala @@ -2,7 +2,7 @@ package cats.tests import cats.{Applicative, Id, Monad, Show} import cats.data.{OptionT, Validated, Xor} -import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadCombineTests, SerializableTests} +import cats.laws.discipline.{ApplicativeTests, FunctorTests, MonadTests, SerializableTests} import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Gen} @@ -146,8 +146,8 @@ class OptionTTests extends CatsSuite { } } - checkAll("OptionT[List, Int]", MonadCombineTests[OptionT[List, ?]].monad[Int, Int, Int]) - checkAll("MonadOptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) + checkAll("Monad[OptionT[List, Int]]", MonadTests[OptionT[List, ?]].monad[Int, Int, Int]) + checkAll("Monad[OptionT[List, ?]]", SerializableTests.serializable(Monad[OptionT[List, ?]])) { implicit val F = ListWrapper.functor From 6c9737750365eb1c9908cd46ad75c95bba82d250 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Wed, 25 Nov 2015 22:33:27 +0000 Subject: [PATCH 14/39] Add some documentation to the Travis script. --- scripts/travis-publish.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/scripts/travis-publish.sh b/scripts/travis-publish.sh index 538abaeed15..d74cdf07993 100755 --- a/scripts/travis-publish.sh +++ b/scripts/travis-publish.sh @@ -1,5 +1,17 @@ #!/bin/bash +# Build Overview: +# The overall build is split into a number of parts +# 1. The build for coverage is performed. This: +# a. First enables the coverage processing, and then +# b. Builds and tests for the JVM using the validateJVM target, and then +# c. Produces the coverage report, and then +# d. Clean is run (as part of coverageReport), to clear down the built artifacts +# 2. The scala js build is executed, compiling the application and testing it for scala js. +# 3. The validateJVM target is executed again, due to the fact that producing coverage with the +# code coverage tool causes the byte code to be instrumented/modified to record the coverage +# metrics when the tests are executing. This causes the full JVM build to be run a second time. + # Example setting to use at command line for testing: # export TRAVIS_SCALA_VERSION=2.10.5;export TRAVIS_PULL_REQUEST="false";export TRAVIS_BRANCH="master" From c9d9795bed69a4995c83b3928eebf5eab5105b6e Mon Sep 17 00:00:00 2001 From: Sarunas Valaskevicius Date: Fri, 27 Nov 2015 21:50:58 +0000 Subject: [PATCH 15/39] fix foldMap stack safety --- free/src/main/scala/cats/free/Free.scala | 15 ++++++++++++- free/src/test/scala/cats/free/FreeTests.scala | 22 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 7acd6be79ab..294319c4e0a 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -130,7 +130,20 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * Run to completion, mapping the suspension with the given transformation at each step and * accumulating into the monad `M`. */ - final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = + @tailrec + final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = { + step match { + case Free.Pure(a) => M.pure(a) + case Free.Suspend(s) => f(s) + case Free.Gosub(c, g) => c match { + case Free.Pure(a) => g(M.pure(a)).foldMap(f) + case Free.Suspend(s) => g(f(s)).foldMap(f) + case Free.Gosub(c1, g1) => g(M.flatMap(c1.foldMapRecursiveStep(f))(cc => g1(cc).foldMapRecursiveStep(f))).foldMap(f) + } + } + } + + private def foldMapRecursiveStep[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = step match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 57f26ef7818..01770147e69 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -28,4 +28,26 @@ class FreeTests extends CatsSuite { x.mapSuspension(NaturalTransformation.id[List]) should === (x) } } + + test("foldMap is stack safe") { + trait FTestApi[A] + case class TB(i: Int) extends FTestApi[Int] + + type FTest[A] = Free[FTestApi, A] + + def tb(i: Int): FTest[Int] = Free.liftF(TB(i)) + + def a(i: Int): FTest[Int] = for { + j <- tb(i) + z <- if (j<10000) a(j) else Free.pure[FTestApi, Int](j) + } yield z + + def runner: FTestApi ~> Id = new (FTestApi ~> Id) { + def apply[A](fa: FTestApi[A]): Id[A] = fa match { + case TB(i) => i+1 + } + } + + assert(10000 == a(0).foldMap(runner)) + } } From 75e07fb914b211b956526ef52f1179ae61d0ae08 Mon Sep 17 00:00:00 2001 From: Sarunas Valaskevicius Date: Fri, 27 Nov 2015 22:59:42 +0000 Subject: [PATCH 16/39] simplify fix --- free/src/main/scala/cats/free/Free.scala | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 294319c4e0a..40bcefcf25a 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -132,23 +132,15 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { */ @tailrec final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = { - step match { - case Free.Pure(a) => M.pure(a) - case Free.Suspend(s) => f(s) - case Free.Gosub(c, g) => c match { - case Free.Pure(a) => g(M.pure(a)).foldMap(f) - case Free.Suspend(s) => g(f(s)).foldMap(f) - case Free.Gosub(c1, g1) => g(M.flatMap(c1.foldMapRecursiveStep(f))(cc => g1(cc).foldMapRecursiveStep(f))).foldMap(f) - } - } - } - - private def foldMapRecursiveStep[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = step match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) - case Gosub(c, g) => M.flatMap(c.foldMap(f))(cc => g(cc).foldMap(f)) + case Gosub(c, g) => c match { + case Suspend(s) => g(f(s)).foldMap(f) + case _ => throw new Error("Unexpected operation. The case should have been eliminated by `step`.") + } } + } /** * Compile your Free into another language by changing the suspension functor From 7486a60f79c5a5c162e4a25c775ce491f0d43218 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 28 Nov 2015 01:19:05 +0000 Subject: [PATCH 17/39] Increase StreamingT tests from Monad to MonadCombine Tests in `StreamingT` called `MonadCombineTests.monad` instead of `monadComine`. Tests for `monadCombine` pass fine so increasing to test this. --- tests/src/test/scala/cats/tests/StreamingTTests.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 61942b75bd3..7edf8673aad 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -10,17 +10,17 @@ import cats.laws.discipline.eq._ class StreamingTTests extends CatsSuite { - checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Eval, ?]", MonadCombineTests[StreamingT[Eval, ?]].monadCombine[Int, Int, Int]) checkAll("StreamingT[Eval, ?]", CoflatMapTests[StreamingT[Eval, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[Eval, Int]", OrderLaws[StreamingT[Eval, Int]].order) checkAll("Monad[StreamingT[Eval, ?]]", SerializableTests.serializable(Monad[StreamingT[Eval, ?]])) - checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[Option, ?]", MonadCombineTests[StreamingT[Option, ?]].monadCombine[Int, Int, Int]) checkAll("StreamingT[Option, ?]", CoflatMapTests[StreamingT[Option, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[Option, Int]", OrderLaws[StreamingT[Option, Int]].order) checkAll("Monad[StreamingT[Option, ?]]", SerializableTests.serializable(Monad[StreamingT[Option, ?]])) - checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monad[Int, Int, Int]) + checkAll("StreamingT[List, ?]", MonadCombineTests[StreamingT[List, ?]].monadCombine[Int, Int, Int]) checkAll("StreamingT[List, ?]", CoflatMapTests[StreamingT[List, ?]].coflatMap[Int, Int, Int]) checkAll("StreamingT[List, Int]", OrderLaws[StreamingT[List, Int]].order) checkAll("Monad[StreamingT[List, ?]]", SerializableTests.serializable(Monad[StreamingT[List, ?]])) From 2dac4f5649b7439aa6778db3f1e70a9f09ee2b16 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 28 Nov 2015 19:04:39 +0000 Subject: [PATCH 18/39] Adds test for Show[Const] Adds a test for Show[Const], includes * Simple test of a specific value * Test that all output starts with Const( * Test that the Show for the contained value is contained in the result * Test that implicitly obtained Show instance produces a consistent result * Tests that retagging the Const does not affect the show result --- tests/src/test/scala/cats/tests/ConstTests.scala | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index 68334f0a6c1..d3bc49c6ab9 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -35,4 +35,19 @@ class ConstTests extends CatsSuite { checkAll("Const[String, Int]", ContravariantTests[Const[String, ?]].contravariant[Int, Int, Int]) checkAll("Contravariant[Const[String, ?]]", SerializableTests.serializable(Contravariant[Const[String, ?]])) + + test("show") { + + Const(1).show should === ("Const(1)") + + forAll { const: Const[Int, String] => + const.show.startsWith("Const(") should === (true) + const.show.contains(const.getConst.show) + const.show should === (implicitly[Show[Const[Int, String]]].show(const)) + const.show should === (const.retag[Boolean].show) + } + } + + + } From 9ffaade82c6f7b10cfc263acecc842894b6ed198 Mon Sep 17 00:00:00 2001 From: Sarunas Valaskevicius Date: Sun, 29 Nov 2015 15:57:20 +0000 Subject: [PATCH 19/39] combine two functions to leverage compiler completencess checks in a case match --- free/src/main/scala/cats/free/Free.scala | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 40bcefcf25a..9ed9f7c1747 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -116,14 +116,6 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { runM2(this) } - /** Takes one evaluation step in the Free monad, re-associating left-nested binds in the process. */ - @tailrec - final def step: Free[S, A] = this match { - case Gosub(Gosub(c, f), g) => c.flatMap(cc => f(cc).flatMap(g)).step - case Gosub(Pure(a), f) => f(a).step - case x => x - } - /** * Catamorphism for `Free`. * @@ -131,16 +123,16 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * accumulating into the monad `M`. */ @tailrec - final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = { - step match { + final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = + this match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) case Gosub(c, g) => c match { case Suspend(s) => g(f(s)).foldMap(f) - case _ => throw new Error("Unexpected operation. The case should have been eliminated by `step`.") + case Gosub(cSub, h) => cSub.flatMap(cc => h(cc).flatMap(g)).foldMap(f) + case Pure(a) => g(a).foldMap(f) } } - } /** * Compile your Free into another language by changing the suspension functor From ec534f635e8ca820c60a3d21ba70c0c38f495479 Mon Sep 17 00:00:00 2001 From: Erik LaBianca Date: Mon, 30 Nov 2015 19:07:59 -0500 Subject: [PATCH 20/39] Add getOrElseF method to XorT --- core/src/main/scala/cats/data/XorT.scala | 7 +++++++ tests/src/test/scala/cats/tests/XorTTests.scala | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 5bc5669bbe9..09907e1691e 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -22,6 +22,13 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { def getOrElse[BB >: B](default: => BB)(implicit F: Functor[F]): F[BB] = F.map(value)(_.getOrElse(default)) + def getOrElseF[BB >: B](default: => F[BB])(implicit F: Monad[F]): F[BB] = { + F.flatMap(value) { + case Xor.Left(_) => default + case Xor.Right(b) => F.pure(b) + } + } + def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(value)(_.recover(pf))) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index de5287af4d8..323888f8330 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -157,6 +157,12 @@ class XorTTests extends CatsSuite { } } + test("getOrElseF with Id consistent with Xor getOrElse") { + forAll { (xort: XorT[Id, String, Int], i: Int) => + xort.getOrElseF(i) should === (xort.value.getOrElse(i)) + } + } + test("forall with Id consistent with Xor forall") { forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => xort.forall(f) should === (xort.value.forall(f)) From 0a2e071f4bf98b68da3f77ed50b54ea7ff5f8fc9 Mon Sep 17 00:00:00 2001 From: Markus Hauck Date: Tue, 1 Dec 2015 21:25:04 +0100 Subject: [PATCH 21/39] Add documentation for `OneAnd` --- docs/src/main/tut/oneand.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/src/main/tut/oneand.md b/docs/src/main/tut/oneand.md index 42cf7e5cd3d..d38cc4fab8e 100644 --- a/docs/src/main/tut/oneand.md +++ b/docs/src/main/tut/oneand.md @@ -7,3 +7,24 @@ scaladoc: "#cats.data.OneAnd" --- # OneAnd +The `OneAnd[F[_],A]` data type represents a single element of type `A` +that is guaranteed to be present (`head`) and in addition to this a +second part that is wrapped inside an higher kinded type constructor +`F[_]`. By choosing the `F` parameter, you can model for example +non-empty lists by choosing `List` for `F`, giving: + +```tut:silent +import cats.data.OneAnd + +type NonEmptyList[A] = OneAnd[List, A] +``` + +which is the actual implementation of non-empty lists in cats. By +having the higher kinded type parameter `F[_]`, `OneAnd` is also able +to represent other "non-empty" data structures, e.g. + +```tut:silent +import cats.data.OneAnd + +type NonEmptyVector[A] = OneAnd[Vector, A] +``` From e4e4618e0515453fc091e1fd8ca3beacb18091bf Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 1 Dec 2015 21:34:01 -0800 Subject: [PATCH 22/39] Add some StateT/State tests --- build.sbt | 2 +- free/src/test/scala/cats/free/FreeTests.scala | 38 ++++++++---- .../cats/laws/discipline/Arbitrary.scala | 2 + .../test/scala/cats/state/StateTTests.scala | 58 +++++++++++++++++-- .../test/scala/cats/tests/FunctionTests.scala | 1 - 5 files changed, 82 insertions(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index f4f738fbbf6..600a3ff05b4 100644 --- a/build.sbt +++ b/build.sbt @@ -153,7 +153,7 @@ lazy val freeJVM = free.jvm lazy val freeJS = free.js lazy val state = crossProject.crossType(CrossType.Pure) - .dependsOn(macros, core, free, tests % "test-internal -> test") + .dependsOn(macros, core, free % "compile-internal;test-internal -> test", tests % "test-internal -> test") .settings(moduleName := "cats-state") .settings(catsSettings:_*) .jsSettings(commonJsSettings:_*) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 01770147e69..28ded453b77 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -4,21 +4,11 @@ package free import cats.arrow.NaturalTransformation import cats.tests.CatsSuite import cats.laws.discipline.{MonadTests, SerializableTests} +import cats.laws.discipline.arbitrary.function0Arbitrary import org.scalacheck.{Arbitrary, Gen} class FreeTests extends CatsSuite { - - implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary( - Gen.oneOf( - A.arbitrary.map(Free.pure[F, A]), - F.arbitrary.map(Free.liftF[F, A]))) - - implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = - new Eq[Free[S, A]] { - def eqv(a: Free[S, A], b: Free[S, A]): Boolean = - SA.eqv(a.runM(identity), b.runM(identity)) - } + import FreeTests._ checkAll("Free[Option, ?]", MonadTests[Free[Option, ?]].monad[Int, Int, Int]) checkAll("Monad[Free[Option, ?]]", SerializableTests.serializable(Monad[Free[Option, ?]])) @@ -51,3 +41,27 @@ class FreeTests extends CatsSuite { assert(10000 == a(0).foldMap(runner)) } } + +object FreeTests extends FreeTestsInstances { + import cats.std.function._ + + implicit def trampolineArbitrary[A:Arbitrary]: Arbitrary[Trampoline[A]] = + freeArbitrary[Function0, A] + + implicit def trampolineEq[A:Eq]: Eq[Trampoline[A]] = + freeEq[Function0, A] +} + +sealed trait FreeTestsInstances { + implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = + Arbitrary( + Gen.oneOf( + A.arbitrary.map(Free.pure[F, A]), + F.arbitrary.map(Free.liftF[F, A]))) + + implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = + new Eq[Free[S, A]] { + def eqv(a: Free[S, A], b: Free[S, A]): Boolean = + SA.eqv(a.runM(identity), b.runM(identity)) + } +} diff --git a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala index ee71f8d6aa8..ec5da2481bb 100644 --- a/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala +++ b/laws/src/main/scala/cats/laws/discipline/Arbitrary.scala @@ -116,6 +116,8 @@ object arbitrary extends ArbitraryInstances0 { implicit def showArbitrary[A: Arbitrary]: Arbitrary[Show[A]] = Arbitrary(Show.fromToString[A]) + implicit def function0Arbitrary[A: Arbitrary]: Arbitrary[() => A] = + Arbitrary(getArbitrary[A].map(() => _)) } private[discipline] sealed trait ArbitraryInstances0 { diff --git a/state/src/test/scala/cats/state/StateTTests.scala b/state/src/test/scala/cats/state/StateTTests.scala index 6e719f26d57..19ca850f981 100644 --- a/state/src/test/scala/cats/state/StateTTests.scala +++ b/state/src/test/scala/cats/state/StateTTests.scala @@ -2,6 +2,7 @@ package cats package state import cats.tests.CatsSuite +import cats.free.FreeTests._ import cats.laws.discipline.{MonadStateTests, MonoidKTests, SerializableTests} import cats.laws.discipline.eq._ import org.scalacheck.{Arbitrary, Gen} @@ -39,18 +40,65 @@ class StateTTests extends CatsSuite { } } + test("runEmpty, runEmptyS, and runEmptyA consistent"){ + forAll { (f: StateT[List, Long, Int]) => + (f.runEmptyS zip f.runEmptyA) should === (f.runEmpty) + } + } + + test("modify identity is a noop"){ + forAll { (f: StateT[List, Long, Int]) => + f.modify(identity) should === (f) + } + } + + test("modify modifies state"){ + forAll { (f: StateT[List, Long, Int], g: Long => Long, initial: Long) => + f.modify(g).runS(initial) should === (f.runS(initial).map(g)) + } + } + + test("modify doesn't affect A value"){ + forAll { (f: StateT[List, Long, Int], g: Long => Long, initial: Long) => + f.modify(g).runA(initial) should === (f.runA(initial)) + } + } + + test("State.modify equivalent to get then set"){ + forAll { (f: Long => Long) => + val s1 = for { + l <- State.get[Long] + _ <- State.set(f(l)) + } yield () + + val s2 = State.modify(f) + + s1 should === (s2) + } + } + checkAll("StateT[Option, Int, Int]", MonadStateTests[StateT[Option, Int, ?], Int].monadState[Int, Int, Int]) checkAll("MonadState[StateT[Option, ?, ?], Int]", SerializableTests.serializable(MonadState[StateT[Option, Int, ?], Int])) + + checkAll("State[Long, ?]", MonadStateTests[State[Long, ?], Long].monadState[Int, Int, Int]) + checkAll("MonadState[State[Long, ?], Long]", SerializableTests.serializable(MonadState[State[Long, ?], Long])) } -object StateTTests { +object StateTTests extends StateTTestsInstances { + implicit def stateEq[S:Eq:Arbitrary, A:Eq]: Eq[State[S, A]] = + stateTEq[free.Trampoline, S, A] - implicit def stateArbitrary[F[_]: Applicative, S, A](implicit F: Arbitrary[S => F[(S, A)]]): Arbitrary[StateT[F, S, A]] = + implicit def stateArbitrary[S: Arbitrary, A: Arbitrary]: Arbitrary[State[S, A]] = + stateTArbitrary[free.Trampoline, S, A] + + val add1: State[Int, Int] = State(n => (n + 1, n)) +} + +sealed trait StateTTestsInstances { + implicit def stateTArbitrary[F[_]: Applicative, S, A](implicit F: Arbitrary[S => F[(S, A)]]): Arbitrary[StateT[F, S, A]] = Arbitrary(F.arbitrary.map(f => StateT(f))) - implicit def stateEq[F[_], S, A](implicit S: Arbitrary[S], FSA: Eq[F[(S, A)]], F: FlatMap[F]): Eq[StateT[F, S, A]] = + implicit def stateTEq[F[_], S, A](implicit S: Arbitrary[S], FSA: Eq[F[(S, A)]], F: FlatMap[F]): Eq[StateT[F, S, A]] = Eq.by[StateT[F, S, A], S => F[(S, A)]](state => s => state.run(s)) - - val add1: State[Int, Int] = State(n => (n + 1, n)) } diff --git a/tests/src/test/scala/cats/tests/FunctionTests.scala b/tests/src/test/scala/cats/tests/FunctionTests.scala index 9a4af0c2681..867dd3c313f 100644 --- a/tests/src/test/scala/cats/tests/FunctionTests.scala +++ b/tests/src/test/scala/cats/tests/FunctionTests.scala @@ -11,7 +11,6 @@ import cats.laws.discipline.arbitrary._ import algebra.laws.GroupLaws class FunctionTests extends CatsSuite { - implicit def ev0[A: Arbitrary]: Arbitrary[() => A] = Arbitrary(Arbitrary.arbitrary[A].map { a => () => a }) checkAll("Function0[Int]", BimonadTests[Function0].bimonad[Int, Int, Int]) checkAll("Bimonad[Function0]", SerializableTests.serializable(Bimonad[Function0])) From 51031635f0d65dfde36f5aa7d615a630539aa138 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Tue, 1 Dec 2015 22:38:20 -0800 Subject: [PATCH 23/39] Test Cokleisli Split instance --- tests/src/test/scala/cats/tests/CokleisliTests.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/src/test/scala/cats/tests/CokleisliTests.scala b/tests/src/test/scala/cats/tests/CokleisliTests.scala index b501edade01..ff5282d5c4d 100644 --- a/tests/src/test/scala/cats/tests/CokleisliTests.scala +++ b/tests/src/test/scala/cats/tests/CokleisliTests.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.arrow.Arrow +import cats.arrow.{Arrow, Split} import cats.data.{Cokleisli, NonEmptyList} import cats.functor.Profunctor import cats.laws.discipline._ @@ -24,6 +24,9 @@ class CokleisliTests extends SlowCatsSuite { checkAll("Cokleisli[Option, Int, Int]", ProfunctorTests[Cokleisli[Option, ?, ?]].profunctor[Int, Int, Int, Int, Int, Int]) checkAll("Profunctor[Cokleisli[Option, ?, ?]", SerializableTests.serializable(Profunctor[Cokleisli[Option, ?, ?]])) + checkAll("Cokleisli[Option, Int, Int]", SplitTests[Cokleisli[Option, ?, ?]].split[Int, Int, Int, Int, Int, Int]) + checkAll("Split[Cokleisli[Option, ?, ?]", SerializableTests.serializable(Split[Cokleisli[Option, ?, ?]])) + { // Ceremony to help scalac to do the right thing, see also #267. type CokleisliNEL[A, B] = Cokleisli[NonEmptyList, A, B] From ecad47acb7543ba47b876ca24a956a873c158851 Mon Sep 17 00:00:00 2001 From: Rob Norris Date: Wed, 2 Dec 2015 00:19:21 -0800 Subject: [PATCH 24/39] added :silent modifier here and there --- docs/src/main/tut/apply.md | 5 ++- docs/src/main/tut/const.md | 26 +++++++-------- docs/src/main/tut/foldable.md | 12 +++++-- docs/src/main/tut/functor.md | 12 +++---- docs/src/main/tut/kleisli.md | 29 +++++++++-------- docs/src/main/tut/monad.md | 6 ++-- docs/src/main/tut/monoid.md | 38 ++++++++++++++-------- docs/src/main/tut/semigroup.md | 31 ++++++++++++------ docs/src/main/tut/semigroupk.md | 20 +++++++++--- docs/src/main/tut/traverse.md | 55 ++++++++++++++++++++------------ docs/src/main/tut/typeclasses.md | 20 +++++++----- docs/src/main/tut/validated.md | 39 ++++++++++++++-------- docs/src/main/tut/xor.md | 22 ++++++------- 13 files changed, 193 insertions(+), 122 deletions(-) diff --git a/docs/src/main/tut/apply.md b/docs/src/main/tut/apply.md index 4650c3b3269..3c56ae6b60d 100644 --- a/docs/src/main/tut/apply.md +++ b/docs/src/main/tut/apply.md @@ -14,8 +14,9 @@ a context can be `Option`, `List` or `Future` for example). However, the difference between `ap` and `map` is that for `ap` the function that takes care of the transformation is of type `F[A => B]`, whereas for `map` it is `A => B`: -```tut +```tut:silent import cats._ + val intToString: Int => String = _.toString val double: Int => Int = _ * 2 val addTwo: Int => Int = _ + 2 @@ -133,8 +134,6 @@ f2(Some(1), Some(2), Some(3)) All instances created by `|@|` have `map`, `ap`, and `tupled` methods of the appropriate arity: ```tut -import cats.syntax.apply._ - val option2 = Option(1) |@| Option(2) val option3 = option2 |@| Option.empty[Int] diff --git a/docs/src/main/tut/const.md b/docs/src/main/tut/const.md index 3ccd9aee7df..d57e00e3b0d 100644 --- a/docs/src/main/tut/const.md +++ b/docs/src/main/tut/const.md @@ -13,13 +13,13 @@ have its uses, which serve as a nice example of the consistency and elegance of ## Thinking about `Const` The `Const` data type can be thought of similarly to the `const` function, but as a data type. -```tut +```tut:silent def const[A, B](a: A)(b: => B): A = a ``` The `const` function takes two arguments and simply returns the first argument, ignoring the second. -```scala +```tut:silent final case class Const[A, B](getConst: A) ``` @@ -44,7 +44,7 @@ to use a lens. A lens can be thought of as a first class getter/setter. A `Lens[S, A]` is a data type that knows how to get an `A` out of an `S`, or set an `A` in an `S`. -```tut +```tut:silent trait Lens[S, A] { def get(s: S): A @@ -58,7 +58,7 @@ trait Lens[S, A] { It can be useful to have effectful modifications as well - perhaps our modification can fail (`Option`) or can return several values (`List`). -```tut +```tut:silent trait Lens[S, A] { def get(s: S): A @@ -78,7 +78,7 @@ trait Lens[S, A] { Note that both `modifyOption` and `modifyList` share the *exact* same implementation. If we look closely, the only thing we need is a `map` operation on the data type. Being good functional programmers, we abstract. -```tut +```tut:silent import cats.Functor import cats.syntax.functor._ @@ -99,7 +99,7 @@ We can redefine `modify` in terms of `modifyF` by using `cats.Id`. We can also t that simply ignores the current value. Due to these modifications however, we must leave `modifyF` abstract since having it defined in terms of `set` would lead to infinite circular calls. -```tut +```tut:silent import cats.Id trait Lens[S, A] { @@ -134,7 +134,7 @@ is to take an `A` and return it right back (lifted into `Const`). Before we plug and play however, note that `modifyF` has a `Functor` constraint on `F[_]`. This means we need to define a `Functor` instance for `Const`, where the first type parameter is fixed. -```tut +```tut:silent import cats.data.Const implicit def constFunctor[X]: Functor[Const[X, ?]] = @@ -147,7 +147,7 @@ implicit def constFunctor[X]: Functor[Const[X, ?]] = Now that that's taken care of, let's substitute and see what happens. -```tut +```tut:silent trait Lens[S, A] { def modifyF[F[_] : Functor](s: S)(f: A => F[A]): F[S] @@ -174,7 +174,7 @@ In the popular [The Essence of the Iterator Pattern](https://www.cs.ox.ac.uk/jer paper, Jeremy Gibbons and Bruno C. d. S. Oliveria describe a functional approach to iterating over a collection of data. Among the abstractions presented are `Foldable` and `Traverse`, replicated below (also available in Cats). -```tut +```tut:silent import cats.{Applicative, Monoid} trait Foldable[F[_]] { @@ -194,7 +194,7 @@ These two type classes seem unrelated - one reduces a collection down to a singl a collection with an effectful function, collecting results. It may be surprising to see that in fact `Traverse` subsumes `Foldable`. -```tut +```tut:silent trait Traverse[F[_]] extends Foldable[F] { def traverse[G[_] : Applicative, A, X](fa: F[A])(f: A => G[X]): G[F[X]] @@ -211,7 +211,7 @@ However, if we imagine `G[_]` to be a sort of type-level constant function, wher `F[X]` is the value we want to ignore, we treat it as the second type parameter and hence, leave it as the free one. -```tut +```tut:silent import cats.data.Const implicit def constApplicative[Z]: Applicative[Const[Z, ?]] = @@ -235,7 +235,7 @@ should try to do something more useful. This suggests composition of `Z`s, which So now we need a constant `Z` value, and a binary function that takes two `Z`s and produces a `Z`. Sound familiar? We want `Z` to have a `Monoid` instance! -```tut +```tut:silent implicit def constApplicative[Z : Monoid]: Applicative[Const[Z, ?]] = new Applicative[Const[Z, ?]] { def pure[A](a: A): Const[Z, A] = Const(Monoid[Z].empty) @@ -261,7 +261,7 @@ So to summarize, what we want is a function `A => Const[B, Nothing]`, and we hav that `Const[B, Z]` (for any `Z`) is the moral equivalent of just `B`, so `A => Const[B, Nothing]` is equivalent to `A => B`, which is exactly what we have, we just need to wrap it. -```tut +```tut:silent trait Traverse[F[_]] extends Foldable[F] { def traverse[G[_] : Applicative, A, X](fa: F[A])(f: A => G[X]): G[F[X]] diff --git a/docs/src/main/tut/foldable.md b/docs/src/main/tut/foldable.md index e707b216660..34ff817440c 100644 --- a/docs/src/main/tut/foldable.md +++ b/docs/src/main/tut/foldable.md @@ -23,10 +23,16 @@ used by the associated `Foldable[_]` instance. These form the basis for many other operations, see also: [A tutorial on the universality and expressiveness of fold](https://www.cs.nott.ac.uk/~gmh/fold.pdf) -```tut +First some standard imports. + +```tut:silent import cats._ import cats.std.all._ +``` + +And examples. +```tut Foldable[List].fold(List("a", "b", "c")) Foldable[List].foldMap(List(1, 2, 4))(_.toString) Foldable[List].foldK(List(List(1,2,3), List(2,3,4))) @@ -70,13 +76,13 @@ Hence when defining some new data structure, if we can define a `foldLeft` and Note that, in order to support laziness, the signature of `Foldable`'s `foldRight` is -``` +```scala def foldRight[A, B](fa: F[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] ``` as opposed to -``` +```scala def foldRight[A, B](fa: F[A], z: B)(f: (A, B) => B): B ``` diff --git a/docs/src/main/tut/functor.md b/docs/src/main/tut/functor.md index a25c589cc66..612bdbbd4d4 100644 --- a/docs/src/main/tut/functor.md +++ b/docs/src/main/tut/functor.md @@ -34,11 +34,13 @@ Vector(1,2,3).map(_.toString) We can trivially create a `Functor` instance for a type which has a well behaved `map` method: -```tut +```tut:silent import cats._ + implicit val optionFunctor: Functor[Option] = new Functor[Option] { def map[A,B](fa: Option[A])(f: A => B) = fa map f } + implicit val listFunctor: Functor[List] = new Functor[List] { def map[A,B](fa: List[A])(f: A => B) = fa map f } @@ -48,7 +50,7 @@ However, functors can also be created for types which don't have a `map` method. For example, if we create a `Functor` for `Function1[In, ?]` we can use `andThen` to implement `map`: -```tut +```tut:silent implicit def function1Functor[In]: Functor[Function1[In, ?]] = new Functor[Function1[In, ?]] { def map[A,B](fa: In => A)(f: A => B): Function1[In,B] = fa andThen f @@ -79,10 +81,8 @@ Functor[List].map(List("qwer", "adsfg"))(len) is a `Some`: ```tut -// Some(x) case: function is applied to x; result is wrapped in Some -Functor[Option].map(Some("adsf"))(len) -// None case: simply returns None (function is not applied) -Functor[Option].map(None)(len) +Functor[Option].map(Some("adsf"))(len) // Some(x) case: function is applied to x; result is wrapped in Some +Functor[Option].map(None)(len) // None case: simply returns None (function is not applied) ``` ## Derived methods diff --git a/docs/src/main/tut/kleisli.md b/docs/src/main/tut/kleisli.md index f9119cb1621..58b09436315 100644 --- a/docs/src/main/tut/kleisli.md +++ b/docs/src/main/tut/kleisli.md @@ -23,7 +23,7 @@ One of the most useful properties of functions is that they **compose**. That is this compositional property that we are able to write many small functions and compose them together to create a larger one that suits our needs. -```tut +```tut:silent val twice: Int => Int = x => x * 2 @@ -31,15 +31,18 @@ val countCats: Int => String = x => if (x == 1) "1 cat" else s"$x cats" val twiceAsManyCats: Int => String = - twice andThen countCats - // equivalent to: countCats compose twice + twice andThen countCats // equivalent to: countCats compose twice +``` +Thus. + +```tut twiceAsManyCats(1) // "2 cats" ``` Sometimes, our functions will need to return monadic values. For instance, consider the following set of functions. -```tut +```tut:silent val parse: String => Option[Int] = s => if (s.matches("-?[0-9]+")) Some(s.toInt) else None @@ -58,7 +61,7 @@ properties of the `F[_]`, we can do different things with `Kleisli`s. For instan `FlatMap[F]` instance (we can call `flatMap` on `F[A]` values), we can compose two `Kleisli`s much like we can two functions. -```tut +```tut:silent import cats.FlatMap import cats.syntax.flatMap._ @@ -70,7 +73,7 @@ final case class Kleisli[F[_], A, B](run: A => F[B]) { Returning to our earlier example: -```tut +```tut:silent // Bring in cats.FlatMap[Option] instance import cats.std.option._ @@ -87,7 +90,7 @@ It is important to note that the `F[_]` having a `FlatMap` (or a `Monad`) instan we can do useful things with weaker requirements. Such an example would be `Kleisli#map`, which only requires that `F[_]` have a `Functor` instance (e.g. is equipped with `map: F[A] => (A => B) => F[B]`). -```tut +```tut:silent import cats.Functor final case class Kleisli[F[_], A, B](run: A => F[B]) { @@ -117,7 +120,7 @@ resolution will pick up the most specific instance it can (depending on the `F[_ An example of a `Monad` instance for `Kleisli` would be: -```tut +```tut:silent import cats.syntax.flatMap._ import cats.syntax.functor._ // Alternatively we can import cats.implicits._ to bring in all the @@ -179,7 +182,7 @@ That is, we take a read-only value, and produce some value with it. For this rea functions often refer to the function as a `Reader`. For instance, it is common to hear about the `Reader` monad. In the same spirit, Cats defines a `Reader` type alias along the lines of: -```tut +```tut:silent // We want A => B, but Kleisli provides A => F[B]. To make the types/shapes match, // we need an F[_] such that providing it a type A is equivalent to A // This can be thought of as the type-level equivalent of the identity function @@ -210,7 +213,7 @@ Let's look at some example modules, where each module has it's own configuration If the configuration is good, we return a `Some` of the module, otherwise a `None`. This example uses `Option` for simplicity - if you want to provide error messages or other failure context, consider using `Xor` instead. -```tut +```tut:silent case class DbConfig(url: String, user: String, pass: String) trait Db object Db { @@ -229,7 +232,7 @@ data over the web). Both depend on their own configuration parameters. Neither k should be. However our application needs both of these modules to work. It is plausible we then have a more global application configuration. -```tut +```tut:silent case class AppConfig(dbConfig: DbConfig, serviceConfig: ServiceConfig) class App(db: Db, service: Service) @@ -239,7 +242,7 @@ As it stands, we cannot use both `Kleisli` validation functions together nicely other a `ServiceConfig`. That means the `FlatMap` (and by extension, the `Monad`) instances differ (recall the input type is fixed in the type class instances). However, there is a nice function on `Kleisli` called `local`. -```tut +```tut:silent final case class Kleisli[F[_], A, B](run: A => F[B]) { def local[AA](f: AA => A): Kleisli[F, AA, B] = Kleisli(f.andThen(run)) } @@ -251,7 +254,7 @@ so long as we tell it how to go from an `AppConfig` to the other configs. Now we can create our application config validator! -```tut +```tut:silent final case class Kleisli[F[_], Z, A](run: Z => F[A]) { def flatMap[B](f: A => Kleisli[F, Z, B])(implicit F: FlatMap[F]): Kleisli[F, Z, B] = Kleisli(z => F.flatMap(run(z))(a => f(a).run(z))) diff --git a/docs/src/main/tut/monad.md b/docs/src/main/tut/monad.md index a751480d9df..84664d00045 100644 --- a/docs/src/main/tut/monad.md +++ b/docs/src/main/tut/monad.md @@ -33,7 +33,7 @@ We can use `flatten` to define `flatMap`: `flatMap` is just `map` followed by `flatten`. Conversely, `flatten` is just `flatMap` using the identity function `x => x` (i.e. `flatMap(_)(x => x)`). -```tut +```tut:silent import cats._ implicit def optionMonad(implicit app: Applicative[Option]) = @@ -52,7 +52,7 @@ implicit def optionMonad(implicit app: Applicative[Option]) = follows this tradition by providing implementations of `flatten` and `map` derived from `flatMap` and `pure`. -```tut +```tut:silent implicit val listMonad = new Monad[List] { def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] = fa.flatMap(f) def pure[A](a: A): List[A] = List(a) @@ -94,7 +94,7 @@ instructions on how to compose any outer monad (`F` in the following example) with a specific inner monad (`Option` in the following example). -```tut +```tut:silent case class OptionT[F[_], A](value: F[Option[A]]) implicit def optionTMonad[F[_]](implicit F : Monad[F]) = { diff --git a/docs/src/main/tut/monoid.md b/docs/src/main/tut/monoid.md index 8ff8f37f7c0..e9f9b6020f8 100644 --- a/docs/src/main/tut/monoid.md +++ b/docs/src/main/tut/monoid.md @@ -12,7 +12,9 @@ source: "/~https://github.com/non/algebra/blob/master/core/src/main/scala/algebra/ value that when combined with any other instance of that type returns the other instance, i.e. - (combine(x, empty) == combine(empty, x) == x) +```scala +(combine(x, empty) == combine(empty, x) == x) +``` For example, if we have a `Monoid[String]` with `combine` defined as string concatenation, then `empty = ""`. @@ -21,11 +23,18 @@ Having an `empty` defined allows us to combine all the elements of some potentially empty collection of `T` for which a `Monoid[T]` is defined and return a `T`, rather than an `Option[T]` as we have a sensible default to fall back to. - -```tut + +First some imports. + +```tut:silent import cats._ import cats.std.all._ +import cats.implicits._ +``` +Examples. + +```tut Monoid[String].empty Monoid[String].combineAll(List("a", "b", "c")) Monoid[String].combineAll(List()) @@ -36,29 +45,26 @@ specific ones for each type, is that we can compose monoids to allow us to operate on more complex types, e.g. ```tut -import cats._ -import cats.std.all._ - Monoid[Map[String,Int]].combineAll(List(Map("a" -> 1, "b" -> 2), Map("a" -> 3))) Monoid[Map[String,Int]].combineAll(List()) ``` This is also true if we define our own instances. As an example, let's use [`Foldable`](foldable.html)'s `foldMap`, which maps over values accumulating -the results, using the available `Monoid` for the type mapped onto. To use this -with a function that produces a tuple, we can define a `Monoid` for a tuple -that will be valid for any tuple where the types it contains also have a -`Monoid` available: +the results, using the available `Monoid` for the type mapped onto. ```tut -import cats._ -import cats.implicits._ - val l = List(1, 2, 3, 4, 5) - l.foldMap(identity) l.foldMap(i => i.toString) +``` +To use this +with a function that produces a tuple, we can define a `Monoid` for a tuple +that will be valid for any tuple where the types it contains also have a +`Monoid` available: + +```tut:silent implicit def tupleMonoid[A : Monoid, B : Monoid]: Monoid[(A, B)] = new Monoid[(A, B)] { def combine(x: (A, B), y: (A, B)): (A, B) = { @@ -68,7 +74,11 @@ implicit def tupleMonoid[A : Monoid, B : Monoid]: Monoid[(A, B)] = } def empty: (A, B) = (Monoid[A].empty, Monoid[B].empty) } +``` + +Thus. +```tut l.foldMap(i => (i, i.toString)) // do both of the above in one pass, hurrah! ``` diff --git a/docs/src/main/tut/semigroup.md b/docs/src/main/tut/semigroup.md index a23ef66c793..1cd396efaf9 100644 --- a/docs/src/main/tut/semigroup.md +++ b/docs/src/main/tut/semigroup.md @@ -12,21 +12,32 @@ A semigroup for some given type A has a single operation returns a value of type A. This operation must be guaranteed to be associative. That is to say that: - ((a combine b) combine c) +```scala +((a combine b) combine c) +``` must be the same as - - (a combine (b combine c)) + +```scala +(a combine (b combine c)) +``` for all possible values of a,b,c. There are instances of `Semigroup` defined for many types found in the scala common library: -```tut +First some imports. + +```tut:silent import cats._ import cats.std.all._ +import cats.implicits._ +``` +Examples. + +```tut Semigroup[Int].combine(1, 2) Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6)) Semigroup[Option[Int]].combine(Option(1), Option(2)) @@ -40,10 +51,7 @@ value of having a `Semigroup` typeclass available is that these compose, so for instance, we can say ```tut -import cats.implicits._ - Map("foo" -> Map("bar" -> 5)).combine(Map("foo" -> Map("bar" -> 6), "baz" -> Map())) - Map("foo" -> List(1, 2)).combine(Map("foo" -> List(3,4), "bar" -> List(42))) ``` @@ -54,12 +62,11 @@ Map("foo" -> Map("bar" -> 5)) ++ Map("foo" -> Map("bar" -> 6), "baz" -> Map()) Map("foo" -> List(1, 2)) ++ Map("foo" -> List(3,4), "bar" -> List(42)) ``` - There is inline syntax available for `Semigroup`. Here we are -following the convention from scalaz, that`|+|` is the +following the convention from scalaz, that `|+|` is the operator from `Semigroup`. -```tut +```tut:silent import cats.syntax.all._ import cats.implicits._ import cats.std._ @@ -67,7 +74,11 @@ import cats.std._ val one = Option(1) val two = Option(2) val n: Option[Int] = None +``` +Thus. + +```tut one |+| two n |+| two n |+| n diff --git a/docs/src/main/tut/semigroupk.md b/docs/src/main/tut/semigroupk.md index d47a1b3bacb..0f3e837883a 100644 --- a/docs/src/main/tut/semigroupk.md +++ b/docs/src/main/tut/semigroupk.md @@ -13,11 +13,15 @@ Before introducing a `SemigroupK`, it makes sense to talk about what a returns a value of type `A`. This operation must be guaranteed to be associative. That is to say that: - ((a combine b) combine c) +```scala +((a combine b) combine c) +``` must be the same as - (a combine (b combine c)) +```scala +(a combine (b combine c)) +``` for all possible values of `a`, `b`, `c`. @@ -33,10 +37,14 @@ defines type aliases to the `Semigroup` from algebra, so that you can There are instances of `Semigroup` defined for many types found in the scala common library: -```tut +```tut:silent import cats._ import cats.std.all._ +``` + +Examples. +```tut Semigroup[Int].combine(1, 2) Semigroup[List[Int]].combine(List(1,2,3), List(4,5,6)) Semigroup[Option[Int]].combine(Option(1), Option(2)) @@ -89,7 +97,7 @@ There is inline syntax available for both `Semigroup` and `|+|` is the operator from semigroup and that `<+>` is the operator from `SemigroupK` (called `Plus` in scalaz). -```tut +```tut:silent import cats.syntax.all._ import cats.implicits._ import cats.std._ @@ -97,7 +105,11 @@ import cats.std._ val one = Option(1) val two = Option(2) val n: Option[Int] = None +``` +Thus. + +```tut one |+| two one <+> two n |+| two diff --git a/docs/src/main/tut/traverse.md b/docs/src/main/tut/traverse.md index f4de95ce4a1..eb6b8e448db 100644 --- a/docs/src/main/tut/traverse.md +++ b/docs/src/main/tut/traverse.md @@ -14,17 +14,20 @@ These effects tend to show up in functions working on a single piece of data - f parsing a single `String` into an `Int`, validating a login, or asynchronously fetching website information for a user. -```tut +```tut:silent +import cats.data.Xor +import scala.concurrent.Future + def parseInt(s: String): Option[Int] = ??? -import cats.data.Xor trait SecurityError trait Credentials + def validateLogin(cred: Credentials): Xor[SecurityError, Unit] = ??? -import scala.concurrent.Future trait Profile trait User + def userInfo(user: User): Future[Profile] = ??? ``` @@ -86,7 +89,7 @@ to allow it to infer the `Applicative[Xor[A, ?]]` and `Applicative[Validated[A, instances - `scalac` has issues inferring the instances for data types that do not trivially satisfy the `F[_]` shape required by `Applicative`. -```tut +```tut:silent import cats.Semigroup import cats.data.{NonEmptyList, OneAnd, Validated, ValidatedNel, Xor} import cats.std.list._ @@ -97,16 +100,27 @@ def parseIntXor(s: String): Xor[NumberFormatException, Int] = def parseIntValidated(s: String): ValidatedNel[NumberFormatException, Int] = Validated.catchOnly[NumberFormatException](s.toInt).toValidatedNel +``` +Examples. + +```tut val x1 = List("1", "2", "3").traverseU(parseIntXor) val x2 = List("1", "abc", "3").traverseU(parseIntXor) val x3 = List("1", "abc", "def").traverseU(parseIntXor) +``` -// Need proof that NonEmptyList[A] is a Semigroup for there to be an -// Applicative instance for ValidatedNel +We need proof that `NonEmptyList[A]` is a `Semigroup `for there to be an `Applicative` instance for +`ValidatedNel`. + +```tut:silent implicit def nelSemigroup[A]: Semigroup[NonEmptyList[A]] = OneAnd.oneAndSemigroupK[List].algebra[A] +``` +Thus. + +```tut val v1 = List("1", "2", "3").traverseU(parseIntValidated) val v2 = List("1", "abc", "3").traverseU(parseIntValidated) val v3 = List("1", "abc", "def").traverseU(parseIntValidated) @@ -133,7 +147,7 @@ a type alias for `Kleisli[Id, E, A]` which is a wrapper around `E => A`. If we fix `E` to be some sort of environment or configuration, we can use the `Reader` applicative in our traverse. -```tut +```tut:silent import cats.data.Reader trait Context @@ -153,8 +167,9 @@ that topic. (Note that since a `Job` is just a `Reader`/`Kleisli`, one could wri Corresponding to our bunches of data are bunches of topics, a `List[Topic]` if you will. Since `Reader` has an `Applicative` instance, we can `traverse` over this list with `processTopic`. -```tut -def processTopics(topics: List[Topic]) = topics.traverse(processTopic) +```tut:silent +def processTopics(topics: List[Topic]) = + topics.traverse(processTopic) ``` Note the nice return type - `Job[List[Result]]`. We now have one aggregate `Job` that when run, @@ -186,9 +201,7 @@ Given `Option` has an `Applicative` instance, we can traverse over the list with ```tut import cats.std.option._ - val l1 = List(Option(1), Option(2), Option(3)).traverse(identity) - val l2 = List(Option(1), None, Option(3)).traverse(identity) ``` @@ -196,7 +209,6 @@ val l2 = List(Option(1), None, Option(3)).traverse(identity) ```tut val l1 = List(Option(1), Option(2), Option(3)).sequence - val l2 = List(Option(1), None, Option(3)).sequence ``` @@ -204,19 +216,19 @@ val l2 = List(Option(1), None, Option(3)).sequence Sometimes our effectful functions return a `Unit` value in cases where there is no interesting value to return (e.g. writing to some sort of store). -```tut +```tut:silent trait Data - def writeToStore(data: Data): Future[Unit] = ??? ``` If we traverse using this, we end up with a funny type. -```tut +```tut:silent import cats.std.future._ import scala.concurrent.ExecutionContext.Implicits.global -def writeManyToStore(data: List[Data]) = data.traverse(writeToStore) +def writeManyToStore(data: List[Data]) = + data.traverse(writeToStore) ``` We end up with a `Future[List[Unit]]`! A `List[Unit]` is not of any use to us, and communicates the @@ -226,13 +238,16 @@ Traversing solely for the sake of the effect (ignoring any values that may be pr is common, so `Foldable` (superclass of `Traverse`) provides `traverse_` and `sequence_` methods that do the same thing as `traverse` and `sequence` but ignores any value produced along the way, returning `Unit` at the end. -```tut +```tut:silent import cats.syntax.foldable._ -def writeManyToStore(data: List[Data]) = data.traverse_(writeToStore) +def writeManyToStore(data: List[Data]) = + data.traverse_(writeToStore) // Int values are ignored with traverse_ -def writeToStoreV2(data: Data): Future[Int] = ??? +def writeToStoreV2(data: Data): Future[Int] = + ??? -def writeManyToStoreV2(data: List[Data]) = data.traverse_(writeToStoreV2) +def writeManyToStoreV2(data: List[Data]) = + data.traverse_(writeToStoreV2) ``` diff --git a/docs/src/main/tut/typeclasses.md b/docs/src/main/tut/typeclasses.md index e698e8a325c..5084b1c5b25 100644 --- a/docs/src/main/tut/typeclasses.md +++ b/docs/src/main/tut/typeclasses.md @@ -4,7 +4,7 @@ The type class pattern is a ubiquitous pattern in Scala, its function is to provide a behavior for some type. You think of it as an "interface" in the Java sense. Here's an example. -```tut +```tut:silent /** * A type class to provide textual representation */ @@ -17,7 +17,7 @@ into `String`s. Now we can write a function which is polymorphic on some `A`, as long as we have some value of `Show[A]`, so that our function can have a way of producing a `String`: -```tut +```tut:silent def log[A](a: A)(implicit s: Show[A]) = println(s.show(a)) ``` @@ -30,11 +30,15 @@ log("a string") It is trivial to supply a `Show` instance for `String`: -```tut +```tut:silent implicit val stringShow = new Show[String] { def show(s: String) = s } -// and now our call to Log succeeds +``` + +and now our call to Log succeeds + +```tut log("a string") ``` @@ -51,7 +55,7 @@ For some types, providing a `Show` instance might depend on having some implicit `Show` instance of some other type, for instance, we could implement `Show` for `Option`: -```tut +```tut:silent implicit def optionShow[A](implicit sa: Show[A]) = new Show[Option[A]] { def show(oa: Option[A]): String = oa match { case None => "None" @@ -69,13 +73,13 @@ log(Option(Option("hello"))) Scala has syntax just for this pattern that we use frequently: -```scala -def log[A : Show](a: A) = println(implicitly[Show[A]].show(a)) +```tut:silent +def log[A: Show](a: A) = println(implicitly[Show[A]].show(a)) ``` is the same as -```scala +```tut:silent def log[A](a: A)(implicit s: Show[A]) = println(s.show(a)) ``` diff --git a/docs/src/main/tut/validated.md b/docs/src/main/tut/validated.md index 2d615851515..81ba76075f6 100644 --- a/docs/src/main/tut/validated.md +++ b/docs/src/main/tut/validated.md @@ -41,7 +41,7 @@ As our running example, we will look at config parsing. Our config will be repre `Map[String, String]`. Parsing will be handled by a `Read` type class - we provide instances just for `String` and `Int` for brevity. -```tut +```tut:silent trait Read[A] { def read(s: String): Option[A] } @@ -65,7 +65,7 @@ Then we enumerate our errors - when asking for a config value, one of two things go wrong: the field is missing, or it is not well-formed with regards to the expected type. -```tut +```tut:silent sealed abstract class ConfigError final case class MissingConfig(field: String) extends ConfigError final case class ParseError(field: String) extends ConfigError @@ -85,7 +85,7 @@ object Validated { Now we are ready to write our parser. -```tut +```tut:silent import cats.data.Validated import cats.data.Validated.{Invalid, Valid} @@ -106,7 +106,7 @@ Everything is in place to write the parallel validator. Recall that we can only validation if each piece is independent. How do we enforce the data is independent? By asking for all of it up front. Let's start with two pieces of data. -```tut +```tut:silent def parallelValidate[E, A, B, C](v1: Validated[E, A], v2: Validated[E, B])(f: (A, B) => C): Validated[E, C] = (v1, v2) match { case (Valid(a), Valid(b)) => Valid(f(a, b)) @@ -122,7 +122,7 @@ but that seems needlessly specific - clients may want to define their own way of How then do we abstract over a binary operation? The `Semigroup` type class captures this idea. -```tut +```tut:silent import cats.Semigroup def parallelValidate[E : Semigroup, A, B, C](v1: Validated[E, A], v2: Validated[E, B])(f: (A, B) => C): Validated[E, C] = @@ -144,7 +144,7 @@ Additionally, the type alias `ValidatedNel[E, A]` is provided. Time to parse. -```tut +```tut:silent import cats.SemigroupK import cats.data.NonEmptyList import cats.std.list._ @@ -158,7 +158,11 @@ implicit val nelSemigroup: Semigroup[NonEmptyList[ConfigError]] = implicit val readString: Read[String] = Read.stringRead implicit val readInt: Read[Int] = Read.intRead +``` + +Any and all errors are reported! +```tut val v1 = parallelValidate(config.parse[String]("url").toValidatedNel, config.parse[Int]("port").toValidatedNel)(ConnectionParams.apply) @@ -170,8 +174,6 @@ val v3 = parallelValidate(config.parse[String]("endpoint").toValidatedNel, config.parse[Int]("port").toValidatedNel)(ConnectionParams.apply) ``` -Any and all errors are reported! - ## Apply Our `parallelValidate` function looks awfully like the `Apply#map2` function. @@ -183,7 +185,7 @@ Which can be defined in terms of `Apply#ap` and `Apply#map`, the very functions Can we perhaps define an `Apply` instance for `Validated`? Better yet, can we define an `Applicative` instance? -```tut +```tut:silent import cats.Applicative implicit def validatedApplicative[E : Semigroup]: Applicative[Validated[E, ?]] = @@ -205,7 +207,7 @@ Awesome! And now we also get access to all the goodness of `Applicative`, among We can now easily ask for several bits of configuration and get any and all errors returned back. -```tut +```tut:silent import cats.Apply import cats.data.ValidatedNel @@ -216,7 +218,11 @@ val config = Config(Map(("name", "cat"), ("age", "not a number"), ("houseNumber" case class Address(houseNumber: Int, street: String) case class Person(name: String, age: Int, address: Address) +``` +Thus. + +```tut val personFromConfig: ValidatedNel[ConfigError, Person] = Apply[ValidatedNel[ConfigError, ?]].map4(config.parse[String]("name").toValidatedNel, config.parse[Int]("age").toValidatedNel, @@ -230,7 +236,7 @@ val personFromConfig: ValidatedNel[ConfigError, Person] = `Option` has `flatMap`, `Xor` has `flatMap`, where's `Validated`'s? Let's try to implement it - better yet, let's implement the `Monad` type class. -```tut +```tut:silent import cats.Monad implicit def validatedMonad[E]: Monad[Validated[E, ?]] = @@ -247,7 +253,7 @@ implicit def validatedMonad[E]: Monad[Validated[E, ?]] = Note that all `Monad` instances are also `Applicative` instances, where `ap` is defined as -```tut +```tut:silent trait Monad[F[_]] { def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B] def pure[A](x: A): F[A] @@ -296,16 +302,21 @@ val houseNumber = config.parse[Int]("house_number").andThen{ n => ### `withXor` The `withXor` method allows you to temporarily turn a `Validated` instance into an `Xor` instance and apply it to a function. -```tut +```tut:silent import cats.data.Xor def positive(field: String, i: Int): ConfigError Xor Int = { if (i >= 0) Xor.right(i) else Xor.left(ParseError(field)) } +``` +Thus. + +```tut val houseNumber = config.parse[Int]("house_number").withXor{ xor: ConfigError Xor Int => xor.flatMap{ i => - positive(i) + positive("house_number", i) } +} ``` diff --git a/docs/src/main/tut/xor.md b/docs/src/main/tut/xor.md index 3c676706d92..80b2099b6a7 100644 --- a/docs/src/main/tut/xor.md +++ b/docs/src/main/tut/xor.md @@ -100,7 +100,7 @@ over `M[_] : Monad`). Since we only ever want the computation to continue in the case of `Xor.Right` (as captured by the right-bias nature), we fix the left type parameter and leave the right one free. -```tut +```tut:silent import cats.Monad implicit def xorMonad[Err]: Monad[Xor[Err, ?]] = @@ -118,7 +118,7 @@ take the reciprocal, and then turn the reciprocal into a string. In exception-throwing code, we would have something like this: -```tut +```tut:silent object ExceptionStyle { def parse(s: String): Int = if (s.matches("-?[0-9]+")) s.toInt @@ -134,7 +134,7 @@ object ExceptionStyle { Instead, let's make the fact that some of our functions can fail explicit in the return type. -```tut +```tut:silent object XorStyle { def parse(s: String): Xor[NumberFormatException, Int] = if (s.matches("-?[0-9]+")) Xor.right(s.toInt) @@ -150,7 +150,7 @@ object XorStyle { Now, using combinators like `flatMap` and `map`, we can compose our functions together. -```tut +```tut:silent import XorStyle._ def magic(s: String): Xor[Exception, String] = @@ -181,7 +181,7 @@ This implies that there is still room to improve. Instead of using exceptions as our error value, let's instead enumerate explicitly the things that can go wrong in our program. -```tut +```tut:silent object XorStyle { sealed abstract class Error final case class NotANumber(string: String) extends Error @@ -221,7 +221,7 @@ magic("123") match { Once you start using `Xor` for all your error-handling, you may quickly run into an issue where you need to call into two separate modules which give back separate kinds of errors. -```tut +```tut:silent sealed abstract class DatabaseError trait DatabaseValue @@ -240,7 +240,7 @@ object Service { Let's say we have an application that wants to do database things, and then take database values and do service things. Glancing at the types, it looks like `flatMap` will do it. -```tut +```tut:silent def doApp = Database.databaseThings().flatMap(Service.serviceThings) ``` @@ -257,7 +257,7 @@ to unify the `E1` and `E2` in a `flatMap` call - in our case, the closest common So clearly in order for us to easily compose `Xor` values, the left type parameter must be the same. We may then be tempted to make our entire application share an error data type. -```tut +```tut:silent sealed abstract class AppError final case object DatabaseError1 extends AppError final case object DatabaseError2 extends AppError @@ -286,7 +286,7 @@ must inspect **all** the `AppError` cases, even though it was only intended for Instead of lumping all our errors into one big ADT, we can instead keep them local to each module, and have an application-wide error ADT that wraps each error ADT we need. -```tut +```tut:silent sealed abstract class DatabaseError trait DatabaseValue @@ -312,7 +312,7 @@ Now in our outer application, we can wrap/lift each module-specific error into ` call our combinators as usual. `Xor` provides a convenient method to assist with this, called `Xor.leftMap` - it can be thought of as the same as `map`, but for the `Left` side. -```tut +```tut:silent def doApp: Xor[AppError, ServiceValue] = Database.databaseThings().leftMap(AppError.Database). flatMap(dv => Service.serviceThings(dv).leftMap(AppError.Service)) @@ -322,7 +322,7 @@ Hurrah! Each module only cares about its own errors as it should be, and more co own error ADT that encapsulates each constituent module's error ADT. Doing this also allows us to take action on entire classes of errors instead of having to pattern match on each individual one. -```tut +```tut:silent def awesome = doApp match { case Xor.Left(AppError.Database(_)) => "something in the database went wrong" From 66297a10fdd1850812bb8bcc135627e681538d4a Mon Sep 17 00:00:00 2001 From: Markus Hauck Date: Wed, 2 Dec 2015 20:15:26 +0100 Subject: [PATCH 25/39] Documentation for the `Invariant` typeclass --- docs/src/main/tut/invariant.md | 107 +++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 docs/src/main/tut/invariant.md diff --git a/docs/src/main/tut/invariant.md b/docs/src/main/tut/invariant.md new file mode 100644 index 00000000000..cda5dbf20f6 --- /dev/null +++ b/docs/src/main/tut/invariant.md @@ -0,0 +1,107 @@ +--- +layout: default +title: "Invariant" +section: "typeclasses" +source: "/~https://github.com/non/cats/blob/master/core/src/main/scala/cats/functor/Invariant.scala" +scaladoc: "#cats.functor.Invariant" +--- +# Invariant + +The `Invariant` typeclass is for functors that define an `imap` +function with the following type: + +```scala +def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] +``` + +Every covariant (as well as contravariant) functor gives rise to an invariant +functor, by ignoring the `f` (or in case of contravariance, `g`) function. + +Examples for instances of `Invariant` are `Semigroup` and `Monoid`, in +the following we will explain why this is the case using `Semigroup`, the +reasoning for `Monoid` is analogous. + +## Invariant instance for Semigroup + +Pretend that we have a `Semigroup[Long]` representing a standard UNIX +timestamp. Let's say that we want to create a `Semigroup[Date]`, by +*reusing* `Semigroup[Long]`. + +### Semigroup does not form a covariant functor + +If `Semigroup` had an instance for the standard covariant `Functor` +typeclass, we could use `map` to apply a function `longToDate`: + +```tut:silent +import java.util.Date +def longToDate: Long => Date = new Date(_) +``` + +But is this enough to give us a `Semigroup[Date]`? The answer is no, +unfortunately. A `Semigroup[Date]` should be able to combine two +values of type `Date`, given a `Semigroup` that only knows how to +combine `Long`s! The `longToDate` function does not help at all, +because it only allows us to convert a `Long` into a `Date`. Seems +like we can't have an `Functor` instance for `Semigroup`. + +### Semigroup does not form a contravariant functor + +On the other side, if `Semigroup` would form a *contravariant* functor +by having an instance for `Contravariant`, we could make use of +`contramap` to apply a function `dateToLong`: + +```tut:silent +import java.util.Date +def dateToLong: Date => Long = _.getTime +``` + +Again we are faced with a problem when trying to get a +`Semigroup[Date]` based on a `Semigroup[Long]`. As before consider +the case where we have two values of `Date` at hand. Using +`dateToLong` we can turn them into `Long`s and use `Semigroup[Long]` +to combine the two values. We are left with a value of type `Long`, +but we can't turn it back into a `Date` using only `contramap`! + +### Semigroup does form an invariant functor + +From the previous discussion we conclude that we need both the `map` +from (covariant) `Functor` and `contramap` from `Contravariant`. +There already is a typeclass for this and it is called `Invariant`. +Instances of the `Invariant` typeclass provide the `imap` function: + +```scala +def imap[A, B](fa: F[A])(f: A => B)(g: B => A): F[B] +``` + +Reusing the example of turning `Semigroup[Long]` into +`Semigroup[Date]`, we can use the `g` parameter to turn `Date` into a +`Long`, combine our two values using `Semigroup[Long]` and then +convert the result back into a `Date` using the `f` parameter of +`imap`: + +```tut:silent +import java.util.Date + +// import everything for simplicity: +import cats._ +import cats.implicits._ + +// or only import what's actually required: +// import cats.Semigroup +// import cats.std.long._ +// import cats.syntax.semigroup._ +// import cats.syntax.invariant._ + +def longToDate: Long => Date = new Date(_) +def dateToLong: Date => Long = _.getTime + +implicit val semigroupDate: Semigroup[Date] = + Semigroup[Long].imap(longToDate)(dateToLong) + +val today: Date = longToDate(1449088684104l) +val timeLeft: Date = longToDate(1900918893l) +``` + +```tut +today |+| timeLeft +``` From de40f839a9276c5a25842f79d781efb9b1235a7d Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 3 Dec 2015 23:05:52 -0800 Subject: [PATCH 26/39] Include Gosub in Free Arbitrary instance Before we were never hitting the Gosub case. This reveals a bug that is causing the "mapSuspension id" test to fail. --- free/src/test/scala/cats/free/FreeTests.scala | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 28ded453b77..dd472f61ad6 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -6,6 +6,7 @@ import cats.tests.CatsSuite import cats.laws.discipline.{MonadTests, SerializableTests} import cats.laws.discipline.arbitrary.function0Arbitrary import org.scalacheck.{Arbitrary, Gen} +import Arbitrary.{arbitrary, arbFunction1} class FreeTests extends CatsSuite { import FreeTests._ @@ -53,11 +54,26 @@ object FreeTests extends FreeTestsInstances { } sealed trait FreeTestsInstances { + private def freeGen[F[_], A](maxDepth: Int)(implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Gen[Free[F, A]] = { + val noGosub = Gen.oneOf( + A.arbitrary.map(Free.pure[F, A]), + F.arbitrary.map(Free.liftF[F, A])) + + val nextDepth = Gen.chooseNum(1, maxDepth - 1) + + def withGosub = for { + fDepth <- nextDepth + freeDepth <- nextDepth + f <- arbFunction1[A, Free[F, A]](Arbitrary(freeGen[F, A](fDepth))).arbitrary + freeFA <- freeGen[F, A](freeDepth) + } yield freeFA.flatMap(f) + + if (maxDepth <= 1) noGosub + else Gen.oneOf(noGosub, withGosub) + } + implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary( - Gen.oneOf( - A.arbitrary.map(Free.pure[F, A]), - F.arbitrary.map(Free.liftF[F, A]))) + Arbitrary(freeGen[F, A](16)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From c587300822ea4b860298f4067dc5b34bc5976aef Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 3 Dec 2015 22:54:20 -0800 Subject: [PATCH 27/39] Revert Free.foldMap regression This reverts most of the changes from #702, because it appears to have caused a regression in the case of Gosub. This commit fixes the "mapSuspension id" unit test and the issue reported in #712. The stack safety test is now failing. I've commented it out for now, because an incorrectness bug seems to be worse than a stack safety issue. --- free/src/main/scala/cats/free/Free.scala | 17 ++++++++++------- free/src/test/scala/cats/free/FreeTests.scala | 4 ++-- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/free/src/main/scala/cats/free/Free.scala b/free/src/main/scala/cats/free/Free.scala index 9ed9f7c1747..585463ade0e 100644 --- a/free/src/main/scala/cats/free/Free.scala +++ b/free/src/main/scala/cats/free/Free.scala @@ -74,6 +74,14 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { final def fold[B](r: A => B, s: S[Free[S, A]] => B)(implicit S: Functor[S]): B = resume.fold(s, r) + /** Takes one evaluation step in the Free monad, re-associating left-nested binds in the process. */ + @tailrec + final def step: Free[S, A] = this match { + case Gosub(Gosub(c, f), g) => c.flatMap(cc => f(cc).flatMap(g)).step + case Gosub(Pure(a), f) => f(a).step + case x => x + } + /** * Evaluate a single layer of the free monad. */ @@ -122,16 +130,11 @@ sealed abstract class Free[S[_], A] extends Product with Serializable { * Run to completion, mapping the suspension with the given transformation at each step and * accumulating into the monad `M`. */ - @tailrec final def foldMap[M[_]](f: S ~> M)(implicit M: Monad[M]): M[A] = - this match { + step match { case Pure(a) => M.pure(a) case Suspend(s) => f(s) - case Gosub(c, g) => c match { - case Suspend(s) => g(f(s)).foldMap(f) - case Gosub(cSub, h) => cSub.flatMap(cc => h(cc).flatMap(g)).foldMap(f) - case Pure(a) => g(a).foldMap(f) - } + case Gosub(c, g) => M.flatMap(c.foldMap(f))(cc => g(cc).foldMap(f)) } /** diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index dd472f61ad6..20b9eac53b2 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -20,7 +20,7 @@ class FreeTests extends CatsSuite { } } - test("foldMap is stack safe") { + ignore("foldMap is stack safe") { trait FTestApi[A] case class TB(i: Int) extends FTestApi[Int] @@ -73,7 +73,7 @@ sealed trait FreeTestsInstances { } implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary(freeGen[F, A](16)) + Arbitrary(freeGen[F, A](8)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From 6feb7d0367b20a3ab2305c7825e5c00a65a72fb8 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Fri, 4 Dec 2015 09:09:11 -0800 Subject: [PATCH 28/39] Reduce depth of arbitrary Free instances --- free/src/test/scala/cats/free/FreeTests.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/free/src/test/scala/cats/free/FreeTests.scala b/free/src/test/scala/cats/free/FreeTests.scala index 20b9eac53b2..fb330a5a5a9 100644 --- a/free/src/test/scala/cats/free/FreeTests.scala +++ b/free/src/test/scala/cats/free/FreeTests.scala @@ -73,7 +73,7 @@ sealed trait FreeTestsInstances { } implicit def freeArbitrary[F[_], A](implicit F: Arbitrary[F[A]], A: Arbitrary[A]): Arbitrary[Free[F, A]] = - Arbitrary(freeGen[F, A](8)) + Arbitrary(freeGen[F, A](6)) implicit def freeEq[S[_]: Monad, A](implicit SA: Eq[S[A]]): Eq[Free[S, A]] = new Eq[Free[S, A]] { From 5471bd4544d8065185e850ed07dc8e254a3a68b6 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Fri, 4 Dec 2015 21:59:01 -0500 Subject: [PATCH 29/39] Add XorT.orElse --- core/src/main/scala/cats/data/XorT.scala | 9 +++++++++ tests/src/test/scala/cats/tests/XorTTests.scala | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/core/src/main/scala/cats/data/XorT.scala b/core/src/main/scala/cats/data/XorT.scala index 77361138267..6bc5c4a23c4 100644 --- a/core/src/main/scala/cats/data/XorT.scala +++ b/core/src/main/scala/cats/data/XorT.scala @@ -29,6 +29,15 @@ final case class XorT[F[_], A, B](value: F[A Xor B]) { } } + def orElse[AA >: A, BB >: B](default: => XorT[F, AA, BB])(implicit F: Monad[F]): XorT[F, AA, BB] = { + XorT(F.flatMap(value) { xor => + xor match { + case Xor.Left(_) => default.value + case _ => F.pure(xor) + } + }) + } + def recover(pf: PartialFunction[A, B])(implicit F: Functor[F]): XorT[F, A, B] = XorT(F.map(value)(_.recover(pf))) diff --git a/tests/src/test/scala/cats/tests/XorTTests.scala b/tests/src/test/scala/cats/tests/XorTTests.scala index 323888f8330..067b9da785f 100644 --- a/tests/src/test/scala/cats/tests/XorTTests.scala +++ b/tests/src/test/scala/cats/tests/XorTTests.scala @@ -163,6 +163,21 @@ class XorTTests extends CatsSuite { } } + test("orElse with Id consistent with Xor orElse") { + forAll { (xort: XorT[Id, String, Int], fallback: XorT[Id, String, Int]) => + xort.orElse(fallback).value should === (xort.value.orElse(fallback.value)) + } + } + + test("orElse evaluates effect only once") { + forAll { (xor: String Xor Int, fallback: XorT[Eval, String, Int]) => + var evals = 0 + val xort = (XorT(Eval.always { evals += 1; xor }) orElse fallback) + xort.value.value + evals should === (1) + } + } + test("forall with Id consistent with Xor forall") { forAll { (xort: XorT[Id, String, Int], f: Int => Boolean) => xort.forall(f) should === (xort.value.forall(f)) From c6a457b1de4d1e73b4249679bbc064a4377951c8 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sat, 5 Dec 2015 11:23:58 +0000 Subject: [PATCH 30/39] Adds Monoid instance for Validated --- core/src/main/scala/cats/data/Validated.scala | 22 ++++++++++++++++++- .../scala/cats/tests/ValidatedTests.scala | 11 +++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index f56c6ee1965..160d29e4e8a 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -168,6 +168,21 @@ sealed abstract class Validated[+E, +A] extends Product with Serializable { case Valid(a) => f(a) case i @ Invalid(_) => i } + + /** + * Combine this `Validated` with another `Validated`, using the `Semigroup` + * instances of the underlying `E` and `A` instances. The resultant `Validated` + * will be `Valid`, if, and only if, both this `Validated` instance and the + * supplied `Validated` instance are also `Valid`. + */ + def combine[EE >: E, AA >: A](that: Validated[EE, AA])(implicit EE: Semigroup[EE], AA: Semigroup[AA]): Validated[EE, AA] = + (this, that) match { + case (Valid(a), Valid(b)) => Valid(AA.combine(a, b)) + case (Invalid(a), Invalid(b)) => Invalid(EE.combine(a, b)) + case (Invalid(_), _) => this + case _ => that + } + } object Validated extends ValidatedInstances with ValidatedFunctions{ @@ -177,6 +192,12 @@ object Validated extends ValidatedInstances with ValidatedFunctions{ private[data] sealed abstract class ValidatedInstances extends ValidatedInstances1 { + + implicit def validatedMonoid[A, B](implicit A: Semigroup[A], B: Monoid[B]): Monoid[Validated[A, B]] = new Monoid[Validated[A, B]] { + def empty: Validated[A, B] = Valid(B.empty) + def combine(x: Validated[A, B], y: Validated[A, B]): Validated[A, B] = x combine y + } + implicit def validatedOrder[A: Order, B: Order]: Order[Validated[A,B]] = new Order[Validated[A,B]] { def compare(x: Validated[A,B], y: Validated[A,B]): Int = x compare y override def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y @@ -282,4 +303,3 @@ trait ValidatedFunctions { */ def fromOption[A, B](o: Option[B], ifNone: => A): Validated[A,B] = o.fold(invalid[A, B](ifNone))(valid) } - diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 1a99d337e71..ba88b5337f8 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -7,7 +7,7 @@ import cats.laws.discipline.{BifunctorTests, TraverseTests, ApplicativeTests, Se import org.scalacheck.{Gen, Arbitrary} import org.scalacheck.Arbitrary._ import cats.laws.discipline.arbitrary._ -import algebra.laws.OrderLaws +import algebra.laws.{OrderLaws, GroupLaws} import scala.util.Try @@ -22,6 +22,8 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) + checkAll("Monoid[Validated[String, Int]]", GroupLaws[Validated[String, Int]].monoid) + { implicit val S = ListWrapper.partialOrder[String] implicit val I = ListWrapper.partialOrder[Int] @@ -143,4 +145,11 @@ class ValidatedTests extends CatsSuite { Validated.fromOption(o, s).toOption should === (o) } } + + test("isValid after combine, iff both are valid") { + forAll { (lhs: Validated[Int, String], rhs: Validated[Int, String]) => + lhs.combine(rhs).isValid should === (lhs.isValid && rhs.isValid) + } + } + } From ed3f7684172c837efe415dbf7c60e3f4792812e6 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 17:15:47 +0000 Subject: [PATCH 31/39] Adds Semigroup for Validated Added a Semigroup for based on comment: /~https://github.com/non/cats/pull/715#issuecomment-162326224 --- core/src/main/scala/cats/data/Validated.scala | 6 ++++++ tests/src/test/scala/cats/tests/ValidatedTests.scala | 2 ++ 2 files changed, 8 insertions(+) diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 160d29e4e8a..4c247ee98d3 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -236,6 +236,12 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance } private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstances2 { + + implicit def validatedSemigroup[A, B](implicit A: Semigroup[A], B: Semigroup[B]): Semigroup[Validated[A, B]] = + new Semigroup[Validated[A, B]] { + def combine(x: Validated[A, B], y: Validated[A, B]): Validated[A, B] = x combine y + } + implicit def validatedPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[Validated[A,B]] = new PartialOrder[Validated[A,B]] { def partialCompare(x: Validated[A,B], y: Validated[A,B]): Double = x partialCompare y diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index ba88b5337f8..2eb135d47dd 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -24,6 +24,8 @@ class ValidatedTests extends CatsSuite { checkAll("Monoid[Validated[String, Int]]", GroupLaws[Validated[String, Int]].monoid) + checkAll("Semigroup[Validated[String, NonEmptyList[Int]]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) + { implicit val S = ListWrapper.partialOrder[String] implicit val I = ListWrapper.partialOrder[Int] From b7dd217aeae43df68ad94c60903622a871ab2bac Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 17:56:34 +0000 Subject: [PATCH 32/39] Adds Semigroup for Xor As per #716, adding a Semigroup for the Xor data type which currently has a Monoid instance but no Semigroup instance. --- core/src/main/scala/cats/data/Xor.scala | 6 ++++++ tests/src/test/scala/cats/tests/XorTests.scala | 7 ++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala/cats/data/Xor.scala b/core/src/main/scala/cats/data/Xor.scala index 19a98fa8d9a..ac5892198be 100644 --- a/core/src/main/scala/cats/data/Xor.scala +++ b/core/src/main/scala/cats/data/Xor.scala @@ -194,6 +194,12 @@ private[data] sealed abstract class XorInstances extends XorInstances1 { } private[data] sealed abstract class XorInstances1 extends XorInstances2 { + + implicit def xorSemigroup[A, B](implicit A: Semigroup[A], B: Semigroup[B]): Semigroup[A Xor B] = + new Semigroup[A Xor B] { + def combine(x: A Xor B, y: A Xor B): A Xor B = x combine y + } + implicit def xorPartialOrder[A: PartialOrder, B: PartialOrder]: PartialOrder[A Xor B] = new PartialOrder[A Xor B] { def partialCompare(x: A Xor B, y: A Xor B): Double = x partialCompare y override def eqv(x: A Xor B, y: A Xor B): Boolean = x === y diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index b968de2e85b..e082be7ed8a 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -1,9 +1,9 @@ package cats package tests -import cats.data.{Xor, XorT} +import cats.data.{NonEmptyList, Xor, XorT} import cats.data.Xor._ -import cats.laws.discipline.arbitrary.xorArbitrary +import cats.laws.discipline.arbitrary._ import cats.laws.discipline.{BifunctorTests, TraverseTests, MonadErrorTests, SerializableTests} import algebra.laws.{GroupLaws, OrderLaws} import org.scalacheck.{Arbitrary, Gen} @@ -12,7 +12,8 @@ import org.scalacheck.Arbitrary._ import scala.util.Try class XorTests extends CatsSuite { - checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) + checkAll("Monoid[Xor[String, Int]]", GroupLaws[Xor[String, Int]].monoid) + checkAll("Semigroup[Xor[String, NonEmptyList[Int]]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) implicit val eq0 = XorT.xorTEq[Xor[String, ?], String, Int] From 6a243c0a84a3645ec05eaba61c20ec804920c45a Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 18:42:48 +0000 Subject: [PATCH 33/39] Update test naming in line with conventions --- tests/src/test/scala/cats/tests/XorTests.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/XorTests.scala b/tests/src/test/scala/cats/tests/XorTests.scala index e082be7ed8a..db0f9d3600e 100644 --- a/tests/src/test/scala/cats/tests/XorTests.scala +++ b/tests/src/test/scala/cats/tests/XorTests.scala @@ -12,8 +12,9 @@ import org.scalacheck.Arbitrary._ import scala.util.Try class XorTests extends CatsSuite { - checkAll("Monoid[Xor[String, Int]]", GroupLaws[Xor[String, Int]].monoid) - checkAll("Semigroup[Xor[String, NonEmptyList[Int]]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) + checkAll("Xor[String, Int]", GroupLaws[Xor[String, Int]].monoid) + + checkAll("Xor[String, NonEmptyList[Int]]", GroupLaws[Xor[String, NonEmptyList[Int]]].semigroup) implicit val eq0 = XorT.xorTEq[Xor[String, ?], String, Int] From ed040de665d6da9fab4101104975db159ba4c930 Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 18:53:42 +0000 Subject: [PATCH 34/39] Update test naming in line with conventions --- tests/src/test/scala/cats/tests/ValidatedTests.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/test/scala/cats/tests/ValidatedTests.scala b/tests/src/test/scala/cats/tests/ValidatedTests.scala index 2eb135d47dd..8c805743880 100644 --- a/tests/src/test/scala/cats/tests/ValidatedTests.scala +++ b/tests/src/test/scala/cats/tests/ValidatedTests.scala @@ -22,9 +22,9 @@ class ValidatedTests extends CatsSuite { checkAll("Validated[String, Int]", OrderLaws[Validated[String, Int]].order) checkAll("Order[Validated[String, Int]]", SerializableTests.serializable(Order[Validated[String, Int]])) - checkAll("Monoid[Validated[String, Int]]", GroupLaws[Validated[String, Int]].monoid) + checkAll("Validated[String, Int]", GroupLaws[Validated[String, Int]].monoid) - checkAll("Semigroup[Validated[String, NonEmptyList[Int]]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) + checkAll("Validated[String, NonEmptyList[Int]]", GroupLaws[Validated[String, NonEmptyList[Int]]].semigroup) { implicit val S = ListWrapper.partialOrder[String] From 2fc8fda9c918eb45637c752b1ac1178f7b518e7f Mon Sep 17 00:00:00 2001 From: Mike Curry Date: Sun, 6 Dec 2015 19:15:34 +0000 Subject: [PATCH 35/39] Adds Semigroup for Const Adding a Semigroup for Const to allow semigroup behaviour for data types with no Monoid instance. --- core/src/main/scala/cats/data/Const.scala | 5 +++++ tests/src/test/scala/cats/tests/ConstTests.scala | 2 ++ 2 files changed, 7 insertions(+) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index ba760ffd6dd..f9b9ea94f02 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -72,6 +72,11 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { } private[data] sealed abstract class ConstInstances0 extends ConstInstances1 { + + implicit def constSemigroup[A: Semigroup, B]: Semigroup[Const[A, B]] = new Semigroup[Const[A, B]] { + def combine(x: Const[A, B], y: Const[A, B]): Const[A, B] = x combine y + } + implicit def constPartialOrder[A: PartialOrder, B]: PartialOrder[Const[A, B]] = new PartialOrder[Const[A, B]]{ def partialCompare(x: Const[A, B], y: Const[A, B]): Double = x partialCompare y diff --git a/tests/src/test/scala/cats/tests/ConstTests.scala b/tests/src/test/scala/cats/tests/ConstTests.scala index d3bc49c6ab9..3866391ae5f 100644 --- a/tests/src/test/scala/cats/tests/ConstTests.scala +++ b/tests/src/test/scala/cats/tests/ConstTests.scala @@ -25,6 +25,8 @@ class ConstTests extends CatsSuite { // Algebra checks for Serializability of instances as part of the laws checkAll("Monoid[Const[Int, String]]", GroupLaws[Const[Int, String]].monoid) + checkAll("Const[NonEmptyList[Int], String]", GroupLaws[Const[NonEmptyList[Int], String]].semigroup) + // Note while Eq is a superclass of PartialOrder and PartialOrder a superclass // of Order, you can get different instances with different (more general) constraints. // For instance, you can get an Order for Const if the first type parameter has an Order, From 76a3a113406ddb92c1f4a2e39dd5ab10d4f4993b Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Sun, 6 Dec 2015 21:41:01 -0800 Subject: [PATCH 36/39] More tut for FreeApplicative --- docs/src/main/tut/freeapplicative.md | 165 ++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 4 deletions(-) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index be14c4d17d2..2fd0f35a15c 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -5,10 +5,167 @@ section: "data" source: "/~https://github.com/non/cats/blob/master/core/src/main/scala/cats/free/FreeApplicative.scala" scaladoc: "#cats.free.FreeApplicative" --- -# Free Applicative Functor +# Free Applicative -Applicative functors are a generalization of monads allowing expressing effectful computations in a pure functional way. +`FreeApplicative`s are similar to `Free` (monads) in that they provide a nice way to represent +computations as data and are useful for building embedded DSLs (EDSLs). However, they differ in +from `Free` in that the kinds of operations they support are limited, much like the distinction +between `Applicative` and `Monad`. -Free Applicative functor is the counterpart of FreeMonads for Applicative. -Free Monads is a construction that is left adjoint to a forgetful functor from the category of Monads +## Example +Consider building an EDSL for validating strings - to keep things simple we'll just have +a way to check a string is at least a certain size and to ensure the string contains numbers. + +```tut:silent +sealed abstract class ValidationOp[A] +case class Size(size: Int) extends ValidationOp[Boolean] +case object HasNumber extends ValidationOp[Boolean] +``` + +Much like the `Free` monad tutorial, we use smart constructors to lift our algebra into the `FreeApplicative`. + +```tut:silent +import cats.free.FreeApplicative +import cats.free.FreeApplicative.lift + +type Validation[A] = FreeApplicative[ValidationOp, A] + +def size(size: Int): Validation[Boolean] = lift(Size(size)) + +val hasNumber: Validation[Boolean] = lift(HasNumber) +``` + +Because a `FreeApplicative` only supports the operations of `Applicative`, we do not get the nicety +of a for-comprehension. We can however still use `Applicative` syntax provided by Cats. + +```tut:silent +import cats.syntax.apply._ + +val prog: Validation[Boolean] = (size(5) |@| hasNumber).map { case (l, r) => l && r} +``` + +As it stands, our program is just an instance of a data structure - nothing has happened +at this point. To make our program useful we need to interpret it. + +```tut +import cats.Id +import cats.arrow.NaturalTransformation +import cats.std.function._ + +val compiler = + new NaturalTransformation[ValidationOp, String => ?] { + def apply[A](fa: ValidationOp[A]): String => A = + str => + fa match { + case Size(size) => str.size >= size + case HasNumber => str.exists(c => "0123456789".contains(c)) + } + } + +val validator = prog.foldMap[String => ?](compiler) +validator("1234") +validator("12345") +``` + +## Differences from `Free` +So far everything we've been doing has been not much different from `Free` - we've built +an algebra and interpreted it. However, there are some things `FreeApplicative` can do that +`Free` cannot. + +Recall a key distinction between the type classes `Applicative` and `Monad` - `Applicative` +captures the idea of independent computations, whereas `Monad` captures that of dependent +computations. Put differently `Applicative`s cannot branch based on the value of an existing/prior +computation. Therefore when using `Applicative`s, we must hand in all our data in one go. + +In the context of `FreeApplicative`s, we can leverage this static knowledge in our interpreter. + +### Parallelism +Because we have everything we need up front and know there can be no branching, we can easily +write a validator that validates in parallel. + +```tut:silent +import cats.data.Kleisli +import cats.std.future._ +import scala.concurrent.Future +import scala.concurrent.ExecutionContext.Implicits.global + +// recall Kleisli[Future, String, A] is the same as String => Future[A] +type ParValidator[A] = Kleisli[Future, String, A] + +val parCompiler = + new NaturalTransformation[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)) } + } + } + } + +val parValidation = prog.foldMap[ParValidator](parCompiler) +``` + +### Logging +We can also write an interpreter that simply creates a list of strings indicating the filters that +have been used - this could be useful for logging purposes. Note that we need not actually evaluate +the rules against a string for this, we simply need to map each rule to some identifier. Therefore +we can completely ignore the return type of the operation and return just a `List[String]` - the +`Const` data type is useful for this. + +```tut:silent +import cats.data.Const +import cats.std.list._ + +type Log[A] = Const[List[String], A] + +val logCompiler = + new NaturalTransformation[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")) + } + } + +val logValidation = prog.foldMap[Log](logCompiler) +``` + +### Why not both? +It is perhaps more plausible and useful to have both the actual validation function and the logging +strings. While we could easily compile our program twice, once for each interpreter as we have above, +we could also do it in one go - this would avoid multiple traversals or the same structure. + +Another useful property `Applicative`s have over `Monad`s is that given two `Applicative`s `F[_]` and +`G[_]`, their product `type FG[A] = (F[A], G[A])` is also an `Applicative`. This is not true in the general +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. + +```tut:silent +import cats.data.Prod + +type ValidateAndLog[A] = Prod[ParValidator, Log, A] + +val prodCompiler = + new NaturalTransformation[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) + } + } + } + +val prodValidation = prog.foldMap[ValidateAndLog](prodCompiler) +``` + +## References Deeper explanations can be found in this paper [Free Applicative Functors by Paolo Capriotti](http://www.paolocapriotti.com/assets/applicative.pdf) From b6736ba726085a8294b839659bd236837ab121cd Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Mon, 7 Dec 2015 09:10:59 -0500 Subject: [PATCH 37/39] Make StreamingT toString more like Streaming toString Before this, StreamingT was always returning "StreamingT(...)" for its toString. It's now similar to Streaming in that it will return "StreamingT()" in the case of Empty and will show the head element in the case of Cons. I also added a pretty weak test for its toString (which actually doesn't capture my changes at all). It may be good to add further tests in the future, but I've got to get to work, and I think this is at least an improvement to the current status. --- core/src/main/scala/cats/data/StreamingT.scala | 9 +++++---- tests/src/test/scala/cats/tests/StreamingTTests.scala | 8 ++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/core/src/main/scala/cats/data/StreamingT.scala b/core/src/main/scala/cats/data/StreamingT.scala index ec5c7500433..76b2a9cf08f 100644 --- a/core/src/main/scala/cats/data/StreamingT.scala +++ b/core/src/main/scala/cats/data/StreamingT.scala @@ -273,11 +273,12 @@ sealed abstract class StreamingT[F[_], A] extends Product with Serializable { lh * This method will not force evaluation of any lazy part of a * stream. As a result, you will see at most one element (the first * one). - * - * Use .toString(n) to see the first n elements of the stream. */ - override def toString: String = - "StreamingT(...)" + override def toString: String = this match { + case Cons(a, _) => s"StreamingT($a, ...)" + case Wait(_) => "StreamingT(...)" + case Empty() => "StreamingT()" + } } object StreamingT extends StreamingTInstances { diff --git a/tests/src/test/scala/cats/tests/StreamingTTests.scala b/tests/src/test/scala/cats/tests/StreamingTTests.scala index 011e55854c3..9ff78dfe228 100644 --- a/tests/src/test/scala/cats/tests/StreamingTTests.scala +++ b/tests/src/test/scala/cats/tests/StreamingTTests.scala @@ -176,6 +176,14 @@ class StreamingTTests extends CatsSuite { StreamingT[Id, Int](x1, x2, tail: _*) should === (fromList) } } + + test("toString is wrapped in StreamingT()"){ + forAll { (xs: StreamingT[Option, Int]) => + val s = xs.toString + s.take(11) should === ("StreamingT(") + s.last should === (')') + } + } } class SpecificStreamingTTests extends CatsSuite { From c4fb1d2ec65e670a533e40d44028fe655c68b8f6 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 7 Dec 2015 09:57:08 -0800 Subject: [PATCH 38/39] Minor fixes/additions to FreeApplicative tut --- docs/src/main/tut/freeapplicative.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index 2fd0f35a15c..9d2f2df8adb 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -47,7 +47,7 @@ val prog: Validation[Boolean] = (size(5) |@| hasNumber).map { case (l, r) => l & As it stands, our program is just an instance of a data structure - nothing has happened at this point. To make our program useful we need to interpret it. -```tut +```tut:silent import cats.Id import cats.arrow.NaturalTransformation import cats.std.function._ @@ -61,7 +61,9 @@ val compiler = case HasNumber => str.exists(c => "0123456789".contains(c)) } } +``` +```tut val validator = prog.foldMap[String => ?](compiler) validator("1234") validator("12345") @@ -128,7 +130,14 @@ val logCompiler = } } -val logValidation = prog.foldMap[Log](logCompiler) +def logValidation[A](validation: Validation[A]): List[String] = + validation.foldMap[Log](logCompiler).getConst +``` + +```tut +logValidation(prog) +logValidation(size(5) *> hasNumber *> size(10)) +logValidation((hasNumber |@| size(3)).map(_ || _)) ``` ### Why not both? From fd06d23e19fd18c4605a600612e3e32c9bd3f1a8 Mon Sep 17 00:00:00 2001 From: Adelbert Chang Date: Mon, 7 Dec 2015 10:59:37 -0800 Subject: [PATCH 39/39] Typos in Free Applicative tut --- docs/src/main/tut/freeapplicative.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/main/tut/freeapplicative.md b/docs/src/main/tut/freeapplicative.md index 9d2f2df8adb..fb605ad00f4 100644 --- a/docs/src/main/tut/freeapplicative.md +++ b/docs/src/main/tut/freeapplicative.md @@ -8,7 +8,7 @@ scaladoc: "#cats.free.FreeApplicative" # Free Applicative `FreeApplicative`s are similar to `Free` (monads) in that they provide a nice way to represent -computations as data and are useful for building embedded DSLs (EDSLs). However, they differ in +computations as data and are useful for building embedded DSLs (EDSLs). However, they differ from `Free` in that the kinds of operations they support are limited, much like the distinction between `Applicative` and `Monad`. @@ -143,7 +143,7 @@ logValidation((hasNumber |@| size(3)).map(_ || _)) ### Why not both? It is perhaps more plausible and useful to have both the actual validation function and the logging strings. While we could easily compile our program twice, once for each interpreter as we have above, -we could also do it in one go - this would avoid multiple traversals or the same structure. +we could also do it in one go - this would avoid multiple traversals of the same structure. Another useful property `Applicative`s have over `Monad`s is that given two `Applicative`s `F[_]` and `G[_]`, their product `type FG[A] = (F[A], G[A])` is also an `Applicative`. This is not true in the general