From b0cd563dfec36de745b3c1e93a2d8f7c5152f667 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 15 Aug 2018 16:07:27 +0200 Subject: [PATCH] Add FunctorEmpty and TraverseEmpty --- core/src/main/scala/cats/FunctorEmpty.scala | 28 +++++++++++ core/src/main/scala/cats/TraverseEmpty.scala | 31 ++++++++++++ core/src/main/scala/cats/data/Const.scala | 19 ++++++++ core/src/main/scala/cats/data/EitherT.scala | 47 +++++++++++++++++++ core/src/main/scala/cats/data/Nested.scala | 41 +++++++++++++++- core/src/main/scala/cats/data/OptionT.scala | 14 ++++++ core/src/main/scala/cats/implicits.scala | 1 + core/src/main/scala/cats/instances/all.scala | 8 ++++ core/src/main/scala/cats/instances/list.scala | 26 ++++++++++ core/src/main/scala/cats/instances/map.scala | 22 +++++++++ .../main/scala/cats/instances/option.scala | 27 +++++++++++ .../main/scala/cats/instances/package.scala | 13 +++-- .../main/scala/cats/instances/sortedMap.scala | 41 +++++++++++++++- .../main/scala/cats/instances/stream.scala | 29 ++++++++++++ .../main/scala/cats/instances/vector.scala | 27 +++++++++++ core/src/main/scala/cats/syntax/all.scala | 2 + .../main/scala/cats/syntax/functorEmpty.scala | 4 ++ core/src/main/scala/cats/syntax/package.scala | 2 + .../scala/cats/syntax/traverseEmpty.scala | 4 ++ .../scala/cats/laws/FunctorEmptyLaws.scala | 38 +++++++++++++++ .../scala/cats/laws/TraverseEmptyLaws.scala | 37 +++++++++++++++ .../laws/discipline/FunctorEmptyTests.scala | 39 +++++++++++++++ .../laws/discipline/TraverseEmptyTests.scala | 43 +++++++++++++++++ .../src/main/scala/cats/tests/CatsSuite.scala | 4 +- .../test/scala/cats/tests/ConstSuite.scala | 3 ++ .../test/scala/cats/tests/EitherTSuite.scala | 22 +++++++++ .../src/test/scala/cats/tests/ListSuite.scala | 6 ++- .../test/scala/cats/tests/ListWrapper.scala | 20 ++++++++ .../src/test/scala/cats/tests/MapSuite.scala | 6 ++- .../test/scala/cats/tests/NestedSuite.scala | 10 ++++ .../test/scala/cats/tests/OptionSuite.scala | 4 ++ .../test/scala/cats/tests/OptionTSuite.scala | 2 + .../scala/cats/tests/SortedMapSuite.scala | 7 ++- .../test/scala/cats/tests/StreamSuite.scala | 6 ++- .../test/scala/cats/tests/VectorSuite.scala | 5 +- 35 files changed, 620 insertions(+), 18 deletions(-) create mode 100644 core/src/main/scala/cats/FunctorEmpty.scala create mode 100644 core/src/main/scala/cats/TraverseEmpty.scala create mode 100644 core/src/main/scala/cats/syntax/functorEmpty.scala create mode 100644 core/src/main/scala/cats/syntax/traverseEmpty.scala create mode 100644 laws/src/main/scala/cats/laws/FunctorEmptyLaws.scala create mode 100644 laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/FunctorEmptyTests.scala create mode 100644 laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala diff --git a/core/src/main/scala/cats/FunctorEmpty.scala b/core/src/main/scala/cats/FunctorEmpty.scala new file mode 100644 index 00000000000..4d1fa6d6cc3 --- /dev/null +++ b/core/src/main/scala/cats/FunctorEmpty.scala @@ -0,0 +1,28 @@ +package cats + +import simulacrum.typeclass + +/** + * `FunctorEmpty[F]` allows you to `map` and filter out elements simultaneously. + */ +@typeclass +trait FunctorEmpty[F[_]] extends Serializable { + def functor: Functor[F] + + def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] + + def collect[A, B](fa: F[A])(f: PartialFunction[A, B]): F[B] = + mapFilter(fa)(f.lift) + + def flattenOption[A](fa: F[Option[A]]): F[A] = + mapFilter(fa)(identity) + + def filter[A](fa: F[A])(f: A => Boolean): F[A] = + mapFilter(fa)(a => if (f(a)) Some(a) else None) +} + +object FunctorEmpty { + implicit def catsFunctorForFunctorEmpty[F[_]](fe: FunctorEmpty[F]): Functor[F] = + fe.functor +} + diff --git a/core/src/main/scala/cats/TraverseEmpty.scala b/core/src/main/scala/cats/TraverseEmpty.scala new file mode 100644 index 00000000000..be0eb8f1622 --- /dev/null +++ b/core/src/main/scala/cats/TraverseEmpty.scala @@ -0,0 +1,31 @@ +package cats + +import simulacrum.typeclass + +/** + * `TraverseEmpty`, also known as `Witherable`, represents list-like structures + * that can essentially have a `traverse` and a `filter` applied as a single + * combined operation (`traverseFilter`). + * + * Based on Haskell's [[https://hackage.haskell.org/package/witherable-0.1.3.3/docs/Data-Witherable.html Data.Witherable]] + */ + +@typeclass +trait TraverseEmpty[F[_]] extends FunctorEmpty[F] { + def traverse: Traverse[F] + + override def functor: Functor[F] = traverse + + def traverseFilter[G[_], A, B](fa: F[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[F[B]] + + def filterA[G[_], A](fa: F[A])(f: A => G[Boolean])(implicit G: Applicative[G]): G[F[A]] = + traverseFilter(fa)(a => G.map(f(a))(if (_) Some(a) else None)) + + override def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] = + traverseFilter[Id, A, B](fa)(f) +} + +object TraverseEmpty { + def catsTraverseForTraverseEmpty[F[_]](te: TraverseEmpty[F]): Traverse[F] = + te.traverse +} diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 305f1fcffb9..6dc80a61e15 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -87,6 +87,25 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { fa.traverse(f) } + def catsDataTraverseEmptyForConst[C]: TraverseEmpty[Const[C, ?]] = new TraverseEmpty[Const[C, ?]] { + + override def mapFilter[A, B](fa: Const[C, A])(f: (A) => Option[B]): Const[C, B] = fa.retag + + override def collect[A, B](fa: Const[C, A])(f: PartialFunction[A, B]): Const[C, B] = fa.retag + + override def flattenOption[A](fa: Const[C, Option[A]]): Const[C, A] = fa.retag + + override def filter[A](fa: Const[C, A])(f: (A) => Boolean): Const[C, A] = fa.retag + + def traverseFilter[G[_], A, B](fa: Const[C, A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[Const[C, B]] = + G.pure(fa.retag[B]) + + override def filterA[G[_], A](fa: Const[C, A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[Const[C, A]] = + G.pure(fa) + + val traverse: Traverse[Const[C, ?]] = Const.catsDataTraverseForConst[C] + } + implicit def catsDataMonoidForConst[A: Monoid, B]: Monoid[Const[A, B]] = new Monoid[Const[A, B]]{ def empty: Const[A, B] = Const.empty diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 5f73115532a..f6446777536 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -3,6 +3,7 @@ package data import cats.Bifunctor import cats.instances.either._ +import cats.instances.option._ import cats.syntax.either._ /** @@ -505,6 +506,28 @@ private[data] abstract class EitherTInstances extends EitherTInstances1 { def defer[A](fa: => EitherT[F, L, A]): EitherT[F, L, A] = EitherT(F.defer(fa.value)) } + + implicit def catsDataTraverseEmptyForEitherT[F[_], L](implicit F0: TraverseEmpty[F]): TraverseEmpty[EitherT[F, L, ?]] = + new EitherTFunctorEmpty[F, L] with TraverseEmpty[EitherT[F, L, ?]] { + implicit def F: FunctorEmpty[F] = F0 + def traverse: Traverse[EitherT[F, L, ?]] = catsDataTraverseForEitherT[F, L](F0.traverse) + + def traverseFilter[G[_], A, B] + (fa: EitherT[F, L, A]) + (f: A => G[Option[B]]) + (implicit G: Applicative[G]): G[EitherT[F, L, B]] = + G.map( + F0.traverseFilter[G, Either[L, A], Either[L, B]](fa.value) { + case l@Left(_) => G.pure(Option(l.rightCast[B])) + case Right(a) => G.map(f(a))(_.map(Either.right)) + })(EitherT(_)) + + override def filterA[G[_], A] + (fa: EitherT[F, L, A]) + (f: A => G[Boolean]) + (implicit G: Applicative[G]): G[EitherT[F, L, A]] = + G.map(F0.filterA(fa.value)(_.fold(_ => G.pure(true), f)))(EitherT[F, L, A]) + } } private[data] abstract class EitherTInstances1 extends EitherTInstances2 { @@ -536,6 +559,9 @@ private[data] abstract class EitherTInstances1 extends EitherTInstances2 { override def ensureOr[A](fa: EitherT[F, L, A])(error: (A) => L)(predicate: (A) => Boolean): EitherT[F, L, A] = fa.ensureOr(error)(predicate)(F) } + + implicit def catsDataFunctorEmptyForEitherT[F[_], L](implicit F0: FunctorEmpty[F]): FunctorEmpty[EitherT[F, L, ?]] = + new EitherTFunctorEmpty[F, L] { implicit def F = F0 } } private[data] abstract class EitherTInstances2 extends EitherTInstances3 { @@ -695,3 +721,24 @@ private[data] sealed trait EitherTOrder[F[_], L, A] extends Order[EitherT[F, L, override def compare(x: EitherT[F, L, A], y: EitherT[F, L, A]): Int = x compare y } + +private[data] sealed trait EitherTFunctorEmpty[F[_], E] extends FunctorEmpty[EitherT[F, E, ?]] { + implicit def F: FunctorEmpty[F] + + override def functor: Functor[EitherT[F, E, ?]] = EitherT.catsDataFunctorForEitherT[F, E](F.functor) + + def mapFilter[A, B](fa: EitherT[F, E, A])(f: (A) => Option[B]): EitherT[F, E, B] = + EitherT[F, E, B](F.mapFilter(fa.value)(_.traverse(f))) + + override def collect[A, B](fa: EitherT[F, E, A])(f: PartialFunction[A, B]): EitherT[F, E, B] = { + EitherT[F, E, B](F.mapFilter(fa.value)(_.traverse(f.lift))) + } + + override def flattenOption[A](fa: EitherT[F, E, Option[A]]): EitherT[F, E, A] = { + EitherT[F, E, A](F.flattenOption[Either[E, A]](F.map(fa.value)(Traverse[Either[E, ?]].sequence[Option, A]))) + } + + override def filter[A](fa: EitherT[F, E, A])(f: (A) => Boolean): EitherT[F, E, A] = { + EitherT[F, E, A](F.filter(fa.value)(_.forall(f))) + } +} diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index f237a56b7a5..efc8f520eca 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -2,7 +2,6 @@ package cats package data - /** Similar to [[cats.data.Tuple2K]], but for nested composition. * * For instance, since both `List` and `Option` have a `Functor`, then so does @@ -54,6 +53,14 @@ private[data] sealed abstract class NestedInstances extends NestedInstances0 { def defer[A](fa: => Nested[F, G, A]): Nested[F, G, A] = Nested(F.defer(fa.value)) } + + implicit def catsDataTraverseEmptyForNested[F[_], G[_]](implicit F0: Traverse[F], G0: TraverseEmpty[G]): TraverseEmpty[Nested[F, G, ?]] = + new NestedTraverseEmpty[F, G] { + implicit val F: Traverse[F] = F0 + implicit val G: TraverseEmpty[G] = G0 + } + + } private[data] sealed abstract class NestedInstances0 extends NestedInstances1 { @@ -315,3 +322,35 @@ private[data] trait NestedInvariantSemigroupalApply[F[_], G[_]] extends Invarian def product[A, B](fa: Nested[F, G, A], fb: Nested[F, G, B]): Nested[F, G, (A, B)] = Nested(FG.product(fa.value, fb.value)) } + +private[data] abstract class NestedTraverseEmpty[F[_], G[_]] extends TraverseEmpty[Nested[F, G, ?]] { + implicit val F: Traverse[F] + + implicit val G: TraverseEmpty[G] + + def traverse: Traverse[Nested[F, G, ?]] = Nested.catsDataTraverseForNested(F, G.traverse) + + override def mapFilter[A, B](fa: Nested[F, G, A])(f: (A) => Option[B]): Nested[F, G, B] = + Nested[F, G, B](F.map(fa.value)(G.mapFilter(_)(f))) + + override def collect[A, B](fa: Nested[F, G, A])(f: PartialFunction[A, B]): Nested[F, G, B] = + Nested[F, G, B](F.map(fa.value)(G.collect(_)(f))) + + override def flattenOption[A](fa: Nested[F, G, Option[A]]): Nested[F, G, A] = + Nested[F, G, A](F.map(fa.value)(G.flattenOption)) + + override def filter[A](fa: Nested[F, G, A])(f: (A) => Boolean): Nested[F, G, A] = + Nested[F, G, A](F.map(fa.value)(G.filter(_)(f))) + + override def filterA[H[_], A] + (fa: Nested[F, G, A]) + (f: A => H[Boolean]) + (implicit H: Applicative[H]): H[Nested[F, G, A]] = + H.map(F.traverse(fa.value)(G.filterA[H, A](_)(f)))(Nested[F, G, A]) + + def traverseFilter[H[_], A, B] + (fga: Nested[F, G, A]) + (f: A => H[Option[B]]) + (implicit H: Applicative[H]): H[Nested[F, G, B]] = + H.map(F.traverse[H, G[A], G[B]](fga.value)(ga => G.traverseFilter(ga)(f)))(Nested[F, G, B]) +} diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 41241e93489..5f62278eed8 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -231,6 +231,20 @@ private[data] sealed abstract class OptionTInstances extends OptionTInstances0 { def defer[A](fa: => OptionT[F, A]): OptionT[F, A] = OptionT(F.defer(fa.value)) } + + implicit def optionTFunctorEmpty[F[_]: Functor]: FunctorEmpty[OptionT[F, ?]] = { + new FunctorEmpty[OptionT[F, ?]] { + override val functor: Functor[OptionT[F, ?]] = OptionT.catsDataFunctorForOptionT[F] + + override def mapFilter[A, B](fa: OptionT[F, A])(f: (A) => Option[B]): OptionT[F, B] = fa.subflatMap(f) + + override def collect[A, B](fa: OptionT[F, A])(f: PartialFunction[A, B]): OptionT[F, B] = fa.subflatMap(f.lift) + + override def flattenOption[A](fa: OptionT[F, Option[A]]): OptionT[F, A] = fa.subflatMap(identity) + + override def filter[A](fa: OptionT[F, A])(f: (A) => Boolean): OptionT[F, A] = fa.filter(f) + } + } } private[data] sealed abstract class OptionTInstances0 extends OptionTInstances1 { diff --git a/core/src/main/scala/cats/implicits.scala b/core/src/main/scala/cats/implicits.scala index 350fc9fb9bd..d89f1f1d468 100644 --- a/core/src/main/scala/cats/implicits.scala +++ b/core/src/main/scala/cats/implicits.scala @@ -7,3 +7,4 @@ object implicits with syntax.AllSyntaxBinCompat2 with instances.AllInstances with instances.AllInstancesBinCompat0 + with instances.AllInstancesBinCompat1 diff --git a/core/src/main/scala/cats/instances/all.scala b/core/src/main/scala/cats/instances/all.scala index d8c3361fa2c..95fa9bc6c82 100644 --- a/core/src/main/scala/cats/instances/all.scala +++ b/core/src/main/scala/cats/instances/all.scala @@ -36,3 +36,11 @@ trait AllInstances trait AllInstancesBinCompat0 extends FunctionInstancesBinCompat0 with Tuple2InstancesBinCompat0 + +trait AllInstancesBinCompat1 + extends OptionInstancesBinCompat0 + with ListInstancesBinCompat0 + with VectorInstancesBinCompat0 + with StreamInstancesBinCompat0 + with MapInstancesBinCompat0 + with SortedMapInstancesBinCompat0 diff --git a/core/src/main/scala/cats/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index 4ba1e829d8d..90761bfa274 100644 --- a/core/src/main/scala/cats/instances/list.scala +++ b/core/src/main/scala/cats/instances/list.scala @@ -143,3 +143,29 @@ trait ListInstances extends cats.kernel.instances.ListInstances { fa.iterator.map(_.show).mkString("List(", ", ", ")") } } + +trait ListInstancesBinCompat0 { + implicit val catsStdTraverseEmptyForList: TraverseEmpty[List] = new TraverseEmpty[List] { + val traverse: Traverse[List] = cats.instances.list.catsStdInstancesForList + + override def mapFilter[A, B](fa: List[A])(f: (A) => Option[B]): List[B] = fa.collect(Function.unlift(f)) + + override def filter[A](fa: List[A])(f: (A) => Boolean): List[A] = fa.filter(f) + + override def collect[A, B](fa: List[A])(f: PartialFunction[A, B]): List[B] = fa.collect(f) + + override def flattenOption[A](fa: List[Option[A]]): List[A] = fa.flatten + + def traverseFilter[G[_], A, B](fa: List[A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[List[B]] = + fa.foldRight(Eval.now(G.pure(List.empty[B])))( + (x, xse) => + G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ :: o)) + ).value + + override def filterA[G[_], A](fa: List[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[List[A]] = + fa.foldRight(Eval.now(G.pure(List.empty[A])))( + (x, xse) => + G.map2Eval(f(x), xse)((b, list) => if (b) x :: list else list) + ).value + } +} diff --git a/core/src/main/scala/cats/instances/map.scala b/core/src/main/scala/cats/instances/map.scala index 573ce855569..48b3ac9ce5e 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -80,3 +80,25 @@ trait MapInstances extends cats.kernel.instances.MapInstances { } // scalastyle:on method.length } + +trait MapInstancesBinCompat0 { + implicit def catsStdFunctorEmptyForMap[K]: FunctorEmpty[Map[K, ?]] = { + new FunctorEmpty[Map[K, ?]] { + + val functor: Functor[Map[K, ?]] = cats.instances.map.catsStdInstancesForMap[K] + + def mapFilter[A, B](fa: Map[K, A])(f: A => Option[B]) = + fa.collect(scala.Function.unlift(t => f(t._2).map(t._1 -> _))) + + override def collect[A, B](fa: Map[K, A])(f: PartialFunction[A, B]) = + fa.collect(scala.Function.unlift(t => f.lift(t._2).map(t._1 -> _))) + + override def flattenOption[A](fa: Map[K, Option[A]]) = + fa.collect(scala.Function.unlift(t => t._2.map(t._1 -> _))) + + override def filter[A](fa: Map[K, A])(f: A => Boolean) = + fa.filter { case (_, v) => f(v) } + + } + } +} diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index 7ca772bc137..a03e004b643 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -121,3 +121,30 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances { } } } + +trait OptionInstancesBinCompat0 { + implicit val catsStdTraverseEmptyForOption: TraverseEmpty[Option] = new TraverseEmpty[Option] { + val traverse: Traverse[Option] = cats.instances.option.catsStdInstancesForOption + + override def mapFilter[A, B](fa: Option[A])(f: (A) => Option[B]): Option[B] = fa.flatMap(f) + + override def filter[A](fa: Option[A])(f: (A) => Boolean): Option[A] = fa.filter(f) + + override def collect[A, B](fa: Option[A])(f: PartialFunction[A, B]): Option[B] = fa.collect(f) + + override def flattenOption[A](fa: Option[Option[A]]): Option[A] = fa.flatten + + def traverseFilter[G[_], A, B](fa: Option[A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[Option[B]] = + fa match { + case _: None.type => G.pure(Option.empty[B]) + case Some(a) => f(a) + } + + override def filterA[G[_], A](fa: Option[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[Option[A]] = + fa match { + case _: None.type => G.pure(Option.empty[A]) + case Some(a) => G.map(f(a))(b => if (b) Some(a) else None) + } + + } +} diff --git a/core/src/main/scala/cats/instances/package.scala b/core/src/main/scala/cats/instances/package.scala index 13f70fc8ee3..e653f8f599b 100644 --- a/core/src/main/scala/cats/instances/package.scala +++ b/core/src/main/scala/cats/instances/package.scala @@ -1,7 +1,7 @@ package cats package object instances { - object all extends AllInstances with AllInstancesBinCompat0 + object all extends AllInstances with AllInstancesBinCompat0 with AllInstancesBinCompat1 object bigInt extends BigIntInstances object bigDecimal extends BigDecimalInstances object bitSet extends BitSetInstances @@ -19,10 +19,10 @@ package object instances { object future extends FutureInstances object int extends IntInstances object invariant extends InvariantMonoidalInstances - object list extends ListInstances + object list extends ListInstances with ListInstancesBinCompat0 object long extends LongInstances object map extends MapInstances - object option extends OptionInstances + object option extends OptionInstances with OptionInstancesBinCompat0 object order extends OrderInstances object ordering extends OrderingInstances object parallel extends ParallelInstances @@ -33,12 +33,11 @@ package object instances { object short extends ShortInstances object sortedMap extends SortedMapInstances object sortedSet extends SortedSetInstances - object stream extends StreamInstances + object stream extends StreamInstances with StreamInstancesBinCompat0 object string extends StringInstances object try_ extends TryInstances - object tuple extends TupleInstances - with Tuple2InstancesBinCompat0 + object tuple extends TupleInstances with Tuple2InstancesBinCompat0 object unit extends UnitInstances object uuid extends UUIDInstances - object vector extends VectorInstances + object vector extends VectorInstances with VectorInstancesBinCompat0 } diff --git a/core/src/main/scala/cats/instances/sortedMap.scala b/core/src/main/scala/cats/instances/sortedMap.scala index 5a25a9c0ca0..708841d1491 100644 --- a/core/src/main/scala/cats/instances/sortedMap.scala +++ b/core/src/main/scala/cats/instances/sortedMap.scala @@ -1,6 +1,6 @@ package cats.instances -import cats.{Always, Applicative, Eval, FlatMap, Foldable, Monoid, Show, Traverse} +import cats.{Always, Applicative, Eval, FlatMap, Foldable, Monoid, Order, Show, Traverse, TraverseEmpty} import cats.kernel._ import cats.kernel.instances.StaticMethods @@ -166,3 +166,42 @@ class SortedMapMonoid[K, V](implicit V: Semigroup[V], O: Order[K]) extends Monoi } } + +trait SortedMapInstancesBinCompat0 { + implicit def catsStdTraverseEmptyForSortedMap[K: Order]: TraverseEmpty[SortedMap[K, ?]] = + new TraverseEmpty[SortedMap[K, ?]] { + + implicit val ordering: Ordering[K] = Order[K].toOrdering + + val traverse: Traverse[SortedMap[K, ?]] = cats.instances.sortedMap.catsStdInstancesForSortedMap[K] + + override def traverseFilter[G[_], A, B] + (fa: SortedMap[K, A]) + (f: A => G[Option[B]]) + (implicit G: Applicative[G]): G[SortedMap[K, B]] = { + val gba: Eval[G[SortedMap[K, B]]] = Always(G.pure(SortedMap.empty)) + Foldable.iterateRight(fa, gba) { (kv, lbuf) => + G.map2Eval(f(kv._2), lbuf)({ (ob, buf) => ob.fold(buf)(b => buf + (kv._1 -> b)) }) + }.value + } + + override def mapFilter[A, B](fa: SortedMap[K, A])(f: (A) => Option[B]): SortedMap[K, B] = + fa.collect(scala.Function.unlift(t => f(t._2).map(t._1 -> _))) + + override def collect[A, B](fa: SortedMap[K, A])(f: PartialFunction[A, B]): SortedMap[K, B] = + fa.collect(scala.Function.unlift(t => f.lift(t._2).map(t._1 -> _))) + + override def flattenOption[A](fa: SortedMap[K, Option[A]]): SortedMap[K, A] = + fa.collect(scala.Function.unlift(t => t._2.map(t._1 -> _))) + + override def filter[A](fa: SortedMap[K, A])(f: (A) => Boolean): SortedMap[K, A] = + fa.filter { case (_, v) => f(v) } + + + override def filterA[G[_], A] + (fa: SortedMap[K, A]) + (f: (A) => G[Boolean]) + (implicit G: Applicative[G]): G[SortedMap[K, A]] = + traverseFilter(fa)(a => G.map(f(a))(if (_) Some(a) else None)) + } +} diff --git a/core/src/main/scala/cats/instances/stream.scala b/core/src/main/scala/cats/instances/stream.scala index 8a4e79c4b33..c33730c315e 100644 --- a/core/src/main/scala/cats/instances/stream.scala +++ b/core/src/main/scala/cats/instances/stream.scala @@ -153,3 +153,32 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances { def show(fa: Stream[A]): String = if (fa.isEmpty) "Stream()" else s"Stream(${fa.head.show}, ?)" } } + +trait StreamInstancesBinCompat0 { + implicit val catsStdTraverseEmptyForStream: TraverseEmpty[Stream] = new TraverseEmpty[Stream] { + val traverse: Traverse[Stream] = cats.instances.stream.catsStdInstancesForStream + + override def mapFilter[A, B](fa: Stream[A])(f: (A) => Option[B]): Stream[B] = { + fa.collect(Function.unlift(f)) + } + + override def filter[A](fa: Stream[A])(f: (A) => Boolean): Stream[A] = fa.filter(f) + + override def collect[A, B](fa: Stream[A])(f: PartialFunction[A, B]): Stream[B] = fa.collect(f) + + override def flattenOption[A](fa: Stream[Option[A]]): Stream[A] = fa.flatten + + def traverseFilter[G[_], A, B](fa: Stream[A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[Stream[B]] = { + fa.foldRight(Eval.now(G.pure(Stream.empty[B])))( + (x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o)) + ).value + } + + override def filterA[G[_], A](fa: Stream[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[Stream[A]] = { + fa.foldRight(Eval.now(G.pure(Stream.empty[A])))( + (x, xse) => G.map2Eval(f(x), xse)((b, as) => if (b) x +: as else as) + ).value + } + + } +} diff --git a/core/src/main/scala/cats/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index a27175daf13..e2bafc1237a 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -2,6 +2,7 @@ package cats package instances import cats.syntax.show._ + import scala.annotation.tailrec import scala.collection.+: import scala.collection.immutable.VectorBuilder @@ -113,3 +114,29 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { fa.iterator.map(_.show).mkString("Vector(", ", ", ")") } } + +trait VectorInstancesBinCompat0 { + implicit val catsStdTraverseEmptyForVector: TraverseEmpty[Vector] = new TraverseEmpty[Vector] { + val traverse: Traverse[Vector] = cats.instances.vector.catsStdInstancesForVector + + override def mapFilter[A, B](fa: Vector[A])(f: (A) => Option[B]): Vector[B] = + fa.collect(Function.unlift(f)) + + override def filter[A](fa: Vector[A])(f: (A) => Boolean): Vector[A] = fa.filter(f) + + override def collect[A, B](fa: Vector[A])(f: PartialFunction[A, B]): Vector[B] = fa.collect(f) + + override def flattenOption[A](fa: Vector[Option[A]]): Vector[A] = fa.flatten + + def traverseFilter[G[_], A, B](fa: Vector[A])(f: (A) => G[Option[B]])(implicit G: Applicative[G]): G[Vector[B]] = + fa.foldRight(Eval.now(G.pure(Vector.empty[B])))( + (x, xse) => G.map2Eval(f(x), xse)((i, o) => i.fold(o)(_ +: o)) + ).value + + override def filterA[G[_], A](fa: Vector[A])(f: (A) => G[Boolean])(implicit G: Applicative[G]): G[Vector[A]] = + fa.foldRight(Eval.now(G.pure(Vector.empty[A])))( + (x, xse) => + G.map2Eval(f(x), xse)((b, vec) => if (b) x +: vec else vec) + ).value + } +} diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index ebee850312e..92d7f2b59d8 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -72,3 +72,5 @@ trait AllSyntaxBinCompat1 trait AllSyntaxBinCompat2 extends ParallelTraverseSyntax + with TraverseEmptySyntax + with FunctorEmptySyntax diff --git a/core/src/main/scala/cats/syntax/functorEmpty.scala b/core/src/main/scala/cats/syntax/functorEmpty.scala new file mode 100644 index 00000000000..ee687de18ff --- /dev/null +++ b/core/src/main/scala/cats/syntax/functorEmpty.scala @@ -0,0 +1,4 @@ +package cats +package syntax + +trait FunctorEmptySyntax extends FunctorEmpty.ToFunctorEmptyOps diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index cf16200fff7..dd6a5fbf562 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -28,6 +28,7 @@ package object syntax { object flatMap extends FlatMapSyntax object foldable extends FoldableSyntax object functor extends FunctorSyntax + object functorEmpty extends FunctorEmptySyntax object group extends GroupSyntax object invariant extends InvariantSyntax object ior extends IorSyntax @@ -49,6 +50,7 @@ package object syntax { object show extends ShowSyntax object strong extends StrongSyntax object traverse extends TraverseSyntax + object traverseEmpty extends TraverseEmptySyntax object nonEmptyTraverse extends NonEmptyTraverseSyntax object unorderedTraverse extends UnorderedTraverseSyntax object validated extends ValidatedSyntax with ValidatedExtensionSyntax diff --git a/core/src/main/scala/cats/syntax/traverseEmpty.scala b/core/src/main/scala/cats/syntax/traverseEmpty.scala new file mode 100644 index 00000000000..bfe49e712f2 --- /dev/null +++ b/core/src/main/scala/cats/syntax/traverseEmpty.scala @@ -0,0 +1,4 @@ +package cats +package syntax + +trait TraverseEmptySyntax extends TraverseEmpty.ToTraverseEmptyOps diff --git a/laws/src/main/scala/cats/laws/FunctorEmptyLaws.scala b/laws/src/main/scala/cats/laws/FunctorEmptyLaws.scala new file mode 100644 index 00000000000..814580896c5 --- /dev/null +++ b/laws/src/main/scala/cats/laws/FunctorEmptyLaws.scala @@ -0,0 +1,38 @@ +package cats +package laws + + +trait FunctorEmptyLaws[F[_]] { + implicit def F: FunctorEmpty[F] + + + implicit def functor: Functor[F] = F.functor + + def mapFilterComposition[A, B, C](fa: F[A], f: A => Option[B], g: B => Option[C]): IsEq[F[C]] = { + val lhs: F[C] = F.mapFilter(F.mapFilter(fa)(f))(g) + val rhs: F[C] = F.mapFilter(fa)(a => f(a).flatMap(g)) + lhs <-> rhs + } + + def mapFilterMapConsistency[A, B](fa: F[A], f: A => B): IsEq[F[B]] = { + F.mapFilter(fa)(f andThen (x => Some(x): Option[B])) <-> functor.map(fa)(f) + } + + def collectConsistentWithMapFilter[A, B](fa: F[A], f: PartialFunction[A, B]): IsEq[F[B]] = { + F.collect(fa)(f) <-> F.mapFilter(fa)(f.lift) + } + + def flattenOptionConsistentWithMapFilter[A](fa: F[Option[A]]): IsEq[F[A]] = { + F.flattenOption(fa) <-> F.mapFilter(fa)(identity) + } + + def filterConsistentWithMapFilter[A](fa: F[A], f: A => Boolean): IsEq[F[A]] = { + F.filter(fa)(f) <-> + F.mapFilter(fa)(a => if (f(a)) Some(a) else None) + } +} + +object FunctorEmptyLaws { + def apply[F[_]](implicit ev: FunctorEmpty[F]): FunctorEmptyLaws[F] = + new FunctorEmptyLaws[F] { def F: FunctorEmpty[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala b/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala new file mode 100644 index 00000000000..06a9e2e8474 --- /dev/null +++ b/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala @@ -0,0 +1,37 @@ +package cats +package laws + +import cats.data.Nested +import cats.syntax.all._ +import cats.instances.option._ + +trait TraverseEmptyLaws[F[_]] extends FunctorEmptyLaws[F] { + implicit override def F: TraverseEmpty[F] + + def traverseFilterIdentity[G[_] : Applicative, A](fa: F[A]): IsEq[G[F[A]]] = { + fa.traverseFilter(_.some.pure[G]) <-> fa.pure[G] + } + + def traverseFilterComposition[A, B, C, M[_], N[_]](fa: F[A], + f: A => M[Option[B]], + g: B => N[Option[C]] + )(implicit + M: Applicative[M], + N: Applicative[N] + ): IsEq[Nested[M, N, F[C]]] = { + val lhs = Nested[M, N, F[C]](fa.traverseFilter(f).map(_.traverseFilter(g))) + val rhs: Nested[M, N, F[C]] = fa.traverseFilter[Nested[M, N, ?], C](a => + Nested[M, N, Option[C]](f(a).map(_.traverseFilter(g))) + ) + lhs <-> rhs + } + + def filterAConsistentWithTraverseFilter[G[_] : Applicative, A](fa: F[A], f: A => G[Boolean]): IsEq[G[F[A]]] = { + fa.filterA(f) <-> fa.traverseFilter(a => f(a).map(if (_) Some(a) else None)) + } +} + +object TraverseEmptyLaws { + def apply[F[_]](implicit ev: TraverseEmpty[F]): TraverseEmptyLaws[F] = + new TraverseEmptyLaws[F] { def F: TraverseEmpty[F] = ev } +} diff --git a/laws/src/main/scala/cats/laws/discipline/FunctorEmptyTests.scala b/laws/src/main/scala/cats/laws/discipline/FunctorEmptyTests.scala new file mode 100644 index 00000000000..38fe5e7a670 --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/FunctorEmptyTests.scala @@ -0,0 +1,39 @@ +package cats +package laws +package discipline + +import org.scalacheck.Prop.forAll +import org.scalacheck.Arbitrary +import org.typelevel.discipline.Laws + +trait FunctorEmptyTests[F[_]] extends Laws { + def laws: FunctorEmptyLaws[F] + + def functorEmpty[A, B, C](implicit + ArbFA: Arbitrary[F[A]], + ArbFABoo: Arbitrary[PartialFunction[A, B]], + ArbFOA: Arbitrary[F[Option[A]]], + ArbAOB: Arbitrary[A => Option[B]], + ArbBOC: Arbitrary[B => Option[C]], + ArbAB: Arbitrary[A => B], + ArbABoo: Arbitrary[A => Boolean], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]] + ): RuleSet = { + new DefaultRuleSet( + name = "functorEmpty", + parent = None, + "mapFilter composition" -> forAll(laws.mapFilterComposition[A, B, C] _), + "mapFilter map consistency" -> forAll(laws.mapFilterMapConsistency[A, B] _), + "collect mapFilter consistency" -> forAll(laws.collectConsistentWithMapFilter[A, B] _), + "flattenOption mapFilter consistency" -> forAll(laws.flattenOptionConsistentWithMapFilter[A] _), + "filter mapFilter consistency" -> forAll(laws.filterConsistentWithMapFilter[A] _) + ) + } +} + +object FunctorEmptyTests { + def apply[F[_]: FunctorEmpty]: FunctorEmptyTests[F] = + new FunctorEmptyTests[F] { def laws: FunctorEmptyLaws[F] = FunctorEmptyLaws[F] } +} diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala new file mode 100644 index 00000000000..985858e80ac --- /dev/null +++ b/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala @@ -0,0 +1,43 @@ +package cats +package laws +package discipline + +import cats.data.Nested +import org.scalacheck.Prop.forAll +import org.scalacheck.Arbitrary +import cats.instances.option._ + +trait TraverseEmptyTests[F[_]] extends FunctorEmptyTests[F] { + def laws: TraverseEmptyLaws[F] + + def traverseEmpty[A, B, C](implicit + ArbFA: Arbitrary[F[A]], + ArbFOA: Arbitrary[F[Option[A]]], + ArbFABoo: Arbitrary[PartialFunction[A, B]], + ArbAOB: Arbitrary[A => Option[B]], + ArbAOOB: Arbitrary[A => Option[Option[B]]], + ArbBOC: Arbitrary[B => Option[C]], + ArbBOOC: Arbitrary[B => Option[Option[C]]], + ArbAB: Arbitrary[A => B], + ArbABoo: Arbitrary[A => Boolean], + ArbAOBoo: Arbitrary[A => Option[Boolean]], + EqFA: Eq[F[A]], + EqFB: Eq[F[B]], + EqFC: Eq[F[C]], + EqGFA: Eq[Option[F[A]]], + EqMNFC: Eq[Nested[Option, Option, F[C]]] + ): RuleSet = { + new DefaultRuleSet( + name = "traverseEmpty", + parent = Some(functorEmpty[A, B, C]), + "traverseFilter identity" -> forAll(laws.traverseFilterIdentity[Option, A] _), + "traverseFilter nested composition" -> forAll(laws.traverseFilterComposition[A, B, C, Option, Option] _), + "filterA consistent with traverseFilter" -> forAll(laws.filterAConsistentWithTraverseFilter[Option, A] _) + ) + } +} + +object TraverseEmptyTests { + def apply[F[_]: TraverseEmpty]: TraverseEmptyTests[F] = + new TraverseEmptyTests[F] { def laws: TraverseEmptyLaws[F] = TraverseEmptyLaws[F] } +} diff --git a/testkit/src/main/scala/cats/tests/CatsSuite.scala b/testkit/src/main/scala/cats/tests/CatsSuite.scala index 617712c2b61..fac621f4dd4 100644 --- a/testkit/src/main/scala/cats/tests/CatsSuite.scala +++ b/testkit/src/main/scala/cats/tests/CatsSuite.scala @@ -2,7 +2,7 @@ package cats package tests import catalysts.Platform -import cats.instances.{AllInstances, AllInstancesBinCompat0} +import cats.instances.{AllInstances, AllInstancesBinCompat0, AllInstancesBinCompat1} import cats.syntax.{AllSyntax, AllSyntaxBinCompat0, AllSyntaxBinCompat1, AllSyntaxBinCompat2, EqOps} import org.scalactic.anyvals.{PosInt, PosZDouble, PosZInt} import org.scalatest.{FunSuite, FunSuiteLike, Matchers} @@ -33,7 +33,7 @@ trait CatsSuite extends FunSuite with GeneratorDrivenPropertyChecks with Discipline with TestSettings - with AllInstances with AllInstancesBinCompat0 + with AllInstances with AllInstancesBinCompat0 with AllInstancesBinCompat1 with AllSyntax with AllSyntaxBinCompat0 with AllSyntaxBinCompat1 with AllSyntaxBinCompat2 with StrictCatsEquality { self: FunSuiteLike => diff --git a/tests/src/test/scala/cats/tests/ConstSuite.scala b/tests/src/test/scala/cats/tests/ConstSuite.scala index 27d1e2c5d39..02c5f6d2613 100644 --- a/tests/src/test/scala/cats/tests/ConstSuite.scala +++ b/tests/src/test/scala/cats/tests/ConstSuite.scala @@ -21,6 +21,9 @@ class ConstSuite extends CatsSuite { checkAll("Const[String, Int] with Option", TraverseTests[Const[String, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Const[String, ?]]", SerializableTests.serializable(Traverse[Const[String, ?]])) + checkAll("Const[String, Int]", TraverseEmptyTests[Const[String, ?]].traverse[Int, Int, Int]) + checkAll("TraverseEmpty[Const[String, ?]]", SerializableTests.serializable(TraverseEmpty[Const[String, ?]])) + // Get Apply[Const[C : Semigroup, ?]], not Applicative[Const[C : Monoid, ?]] { implicit def nonEmptyListSemigroup[A]: Semigroup[NonEmptyList[A]] = SemigroupK[NonEmptyList].algebra diff --git a/tests/src/test/scala/cats/tests/EitherTSuite.scala b/tests/src/test/scala/cats/tests/EitherTSuite.scala index 4e7f198082a..296453228cd 100644 --- a/tests/src/test/scala/cats/tests/EitherTSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherTSuite.scala @@ -47,6 +47,28 @@ class EitherTSuite extends CatsSuite { } + { + //If a TraverseEmpty for F is defined + implicit val F = ListWrapper.traverseEmpty + + checkAll("EitherT[ListWrapper, Int, ?]", + TraverseEmptyTests[EitherT[ListWrapper, Int, ?]].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[EitherT[ListWrapper, Int, ?]]", + SerializableTests.serializable(TraverseEmpty[EitherT[ListWrapper, Int, ?]])) + + } + + { + //If a FunctorEmpty for F is defined + implicit val F = ListWrapper.functorEmpty + + checkAll("EitherT[ListWrapper, Int, ?]", + FunctorEmptyTests[EitherT[ListWrapper, Int, ?]].functorEmpty[Int, Int, Int]) + checkAll("FunctorEmpty[EitherT[ListWrapper, Int, ?]]", + SerializableTests.serializable(FunctorEmpty[EitherT[ListWrapper, Int, ?]])) + + } + { //if a Monad is defined diff --git a/tests/src/test/scala/cats/tests/ListSuite.scala b/tests/src/test/scala/cats/tests/ListSuite.scala index a75de048795..a74cbcc77a9 100644 --- a/tests/src/test/scala/cats/tests/ListSuite.scala +++ b/tests/src/test/scala/cats/tests/ListSuite.scala @@ -2,7 +2,7 @@ package cats package tests import cats.data.{NonEmptyList, ZipList} -import cats.laws.discipline.{CommutativeApplyTests, TraverseTests, CoflatMapTests, AlternativeTests, MonadTests, SerializableTests, SemigroupalTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} import cats.laws.discipline.arbitrary._ class ListSuite extends CatsSuite { @@ -22,6 +22,10 @@ class ListSuite extends CatsSuite { checkAll("List[Int]", MonadTests[List].monad[Int, Int, Int]) checkAll("Monad[List]", SerializableTests.serializable(Monad[List])) + checkAll("List[Int]", TraverseEmptyTests[List].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[List]", SerializableTests.serializable(TraverseEmpty[List])) + + checkAll("ZipList[Int]", CommutativeApplyTests[ZipList].commutativeApply[Int, Int, Int]) test("nel => list => nel returns original nel")( diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 212d9d61ce4..2384c64d52a 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -56,6 +56,26 @@ object ListWrapper { } } + val traverseEmpty: TraverseEmpty[ListWrapper] = { + val F = TraverseEmpty[List] + + new TraverseEmpty[ListWrapper] { + def traverse = ListWrapper.traverse + def traverseFilter[G[_], A, B](fa: ListWrapper[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[ListWrapper[B]] = + G.map(F.traverseFilter(fa.list)(f))(ListWrapper.apply) + } + } + + val functorEmpty: FunctorEmpty[ListWrapper] = { + val F = FunctorEmpty[List] + + new FunctorEmpty[ListWrapper] { + def functor = ListWrapper.functor + def mapFilter[A, B](fa: ListWrapper[A])(f: A => Option[B]): ListWrapper[B] = + ListWrapper(F.mapFilter(fa.list)(f)) + } + } + val foldable: Foldable[ListWrapper] = traverse val functor: Functor[ListWrapper] = traverse diff --git a/tests/src/test/scala/cats/tests/MapSuite.scala b/tests/src/test/scala/cats/tests/MapSuite.scala index 9be630d7b55..6ad2c687ddf 100644 --- a/tests/src/test/scala/cats/tests/MapSuite.scala +++ b/tests/src/test/scala/cats/tests/MapSuite.scala @@ -1,7 +1,8 @@ package cats package tests -import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests} +import cats.laws.discipline.{FlatMapTests, FunctorEmptyTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests} +import cats.laws.discipline.arbitrary._ class MapSuite extends CatsSuite { implicit val iso = SemigroupalTests.Isomorphisms.invariant[Map[Int, ?]] @@ -15,6 +16,9 @@ class MapSuite extends CatsSuite { checkAll("Map[Int, Int] with Option", UnorderedTraverseTests[Map[Int, ?]].unorderedTraverse[Int, Int, Int, Option, Option]) checkAll("UnorderedTraverse[Map[Int, ?]]", SerializableTests.serializable(UnorderedTraverse[Map[Int, ?]])) + checkAll("Map[Int, Int]", FunctorEmptyTests[Map[Int, ?]].functorEmpty[Int, Int, Int]) + checkAll("FunctorEmpty[Map[Int, ?]]", SerializableTests.serializable(FunctorEmpty[Map[Int, ?]])) + test("show isn't empty and is formatted as expected") { forAll { (map: Map[Int, String]) => diff --git a/tests/src/test/scala/cats/tests/NestedSuite.scala b/tests/src/test/scala/cats/tests/NestedSuite.scala index df200b48a79..f5bd48f17eb 100644 --- a/tests/src/test/scala/cats/tests/NestedSuite.scala +++ b/tests/src/test/scala/cats/tests/NestedSuite.scala @@ -25,6 +25,16 @@ class NestedSuite extends CatsSuite { checkAll("Invariant[Nested[ListWrapper, ListWrapper, ?]]", SerializableTests.serializable(Invariant[Nested[ListWrapper, ListWrapper, ?]])) } + { + // TraverseEmpty composition + implicit val instance = ListWrapper.traverseEmpty + implicit val traverseInstance = ListWrapper.traverse + checkAll("Nested[ListWrapper, ListWrapper]", + TraverseEmptyTests[Nested[ListWrapper, ListWrapper, ?]].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[Nested[ListWrapper, ListWrapper, ?]]", + SerializableTests.serializable(TraverseEmpty[Nested[ListWrapper, ListWrapper, ?]])) + } + { // Invariant + Covariant = Invariant val instance = Nested.catsDataInvariantForCovariantNested(ListWrapper.invariant, ListWrapper.functor) diff --git a/tests/src/test/scala/cats/tests/OptionSuite.scala b/tests/src/test/scala/cats/tests/OptionSuite.scala index a8f1dbc3aa9..8604b3b7d92 100644 --- a/tests/src/test/scala/cats/tests/OptionSuite.scala +++ b/tests/src/test/scala/cats/tests/OptionSuite.scala @@ -3,6 +3,7 @@ package tests import cats.laws.{ApplicativeLaws, CoflatMapLaws, FlatMapLaws, MonadLaws} import cats.laws.discipline._ +import cats.laws.discipline.arbitrary._ class OptionSuite extends CatsSuite { checkAll("Option[Int]", SemigroupalTests[Option].semigroupal[Int, Int, Int]) @@ -20,6 +21,9 @@ class OptionSuite extends CatsSuite { checkAll("Option[Int] with Option", TraverseTests[Option].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[Option]", SerializableTests.serializable(Traverse[Option])) + checkAll("Option[Int] with Option", TraverseEmptyTests[Option].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[Option]", SerializableTests.serializable(TraverseEmpty[Option])) + checkAll("Option with Unit", MonadErrorTests[Option, Unit].monadError[Int, Int, Int]) checkAll("MonadError[Option, Unit]", SerializableTests.serializable(MonadError[Option, Unit])) diff --git a/tests/src/test/scala/cats/tests/OptionTSuite.scala b/tests/src/test/scala/cats/tests/OptionTSuite.scala index 4b3623d4129..0d67029de45 100644 --- a/tests/src/test/scala/cats/tests/OptionTSuite.scala +++ b/tests/src/test/scala/cats/tests/OptionTSuite.scala @@ -10,6 +10,8 @@ class OptionTSuite extends CatsSuite { implicit val iso = SemigroupalTests.Isomorphisms.invariant[OptionT[ListWrapper, ?]](OptionT.catsDataFunctorForOptionT(ListWrapper.functor)) checkAll("OptionT[Eval, ?]", DeferTests[OptionT[Eval, ?]].defer[Int]) + checkAll("OptionT[Eval, ?]", FunctorEmptyTests[OptionT[Eval, ?]].functorEmpty[Int, Int, Int]) + { implicit val F = ListWrapper.eqv[Option[Int]] diff --git a/tests/src/test/scala/cats/tests/SortedMapSuite.scala b/tests/src/test/scala/cats/tests/SortedMapSuite.scala index 33693c990b9..13283fe6623 100644 --- a/tests/src/test/scala/cats/tests/SortedMapSuite.scala +++ b/tests/src/test/scala/cats/tests/SortedMapSuite.scala @@ -2,8 +2,8 @@ package cats package tests import cats.kernel.CommutativeMonoid -import cats.kernel.laws.discipline.{HashTests, CommutativeMonoidTests, MonoidTests} -import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, TraverseTests} +import cats.kernel.laws.discipline.{CommutativeMonoidTests, HashTests, MonoidTests} +import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} import cats.laws.discipline.arbitrary._ import scala.collection.immutable.SortedMap @@ -20,6 +20,9 @@ class SortedMapSuite extends CatsSuite { checkAll("SortedMap[Int, Int] with Option", TraverseTests[SortedMap[Int, ?]].traverse[Int, Int, Int, Int, Option, Option]) checkAll("Traverse[SortedMap[Int, ?]]", SerializableTests.serializable(Traverse[SortedMap[Int, ?]])) + checkAll("SortedMap[Int, Int]", TraverseEmptyTests[SortedMap[Int, ?]].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[SortedMap[Int, ?]]", SerializableTests.serializable(TraverseEmpty[SortedMap[Int, ?]])) + test("show isn't empty and is formatted as expected") { forAll { (map: SortedMap[Int, String]) => map.show.nonEmpty should === (true) diff --git a/tests/src/test/scala/cats/tests/StreamSuite.scala b/tests/src/test/scala/cats/tests/StreamSuite.scala index bfc4cb51ef2..75a7a5d1beb 100644 --- a/tests/src/test/scala/cats/tests/StreamSuite.scala +++ b/tests/src/test/scala/cats/tests/StreamSuite.scala @@ -1,9 +1,8 @@ package cats package tests -import cats.laws.discipline.{CommutativeApplyTests, CoflatMapTests, MonadTests, AlternativeTests, SerializableTests, TraverseTests, SemigroupalTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} import cats.data.ZipStream - import cats.laws.discipline.arbitrary._ class StreamSuite extends CatsSuite { @@ -22,6 +21,9 @@ class StreamSuite extends CatsSuite { checkAll("Stream[Int] with Option", TraverseTests[Stream].traverse[Int, Int, Int, Set[Int], Option, Option]) checkAll("Traverse[Stream]", SerializableTests.serializable(Traverse[Stream])) + checkAll("Stream[Int]", TraverseEmptyTests[Stream].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[Stream]", SerializableTests.serializable(TraverseEmpty[Stream])) + // Can't test applicative laws as they don't terminate checkAll("ZipStream[Int]", CommutativeApplyTests[ZipStream].apply[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/VectorSuite.scala b/tests/src/test/scala/cats/tests/VectorSuite.scala index 2ae3ce5f6ab..aff1464dddd 100644 --- a/tests/src/test/scala/cats/tests/VectorSuite.scala +++ b/tests/src/test/scala/cats/tests/VectorSuite.scala @@ -2,7 +2,7 @@ package cats package tests import cats.data.{NonEmptyVector, ZipVector} -import cats.laws.discipline.{CommutativeApplyTests, AlternativeTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests, SemigroupalTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} import cats.laws.discipline.arbitrary._ class VectorSuite extends CatsSuite { @@ -21,6 +21,9 @@ class VectorSuite extends CatsSuite { checkAll("Vector[Int]", MonadTests[Vector].monad[Int, Int, Int]) checkAll("Monad[Vector]", SerializableTests.serializable(Monad[Vector])) + checkAll("Vector[Int]", TraverseEmptyTests[Vector].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[Vector]", SerializableTests.serializable(TraverseEmpty[Vector])) + checkAll("ZipVector[Int]", CommutativeApplyTests[ZipVector].commutativeApply[Int, Int, Int]) test("show") {