From 9c9d9deac801a4acb80bfd4281f3da4b6781dcdd Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Wed, 15 Aug 2018 16:07:27 +0200 Subject: [PATCH 1/9] 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 0000000000..4d1fa6d6cc --- /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 0000000000..be0eb8f162 --- /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 305f1fcffb..e8beb676d8 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) } + implicit 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 5f73115532..f644677753 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 f237a56b7a..efc8f520ec 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 41241e9348..5f62278eed 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 350fc9fb9b..d89f1f1d46 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 d8c3361fa2..95fa9bc6c8 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 4ba1e829d8..90761bfa27 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 573ce85556..48b3ac9ce5 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 7ca772bc13..a03e004b64 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 13f70fc8ee..e653f8f599 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 5a25a9c0ca..708841d149 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 8a4e79c4b3..c33730c315 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 a27175daf1..e2bafc1237 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 ebee850312..92d7f2b59d 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 0000000000..ee687de18f --- /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 cf16200fff..dd6a5fbf56 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 0000000000..bfe49e712f --- /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 0000000000..814580896c --- /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 0000000000..06a9e2e847 --- /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 0000000000..38fe5e7a67 --- /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 0000000000..985858e80a --- /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 617712c2b6..fac621f4dd 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 27d1e2c5d3..e1f7756da7 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, ?]].traverseEmpty[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 4e7f198082..296453228c 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 a75de04879..a74cbcc77a 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 212d9d61ce..2384c64d52 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 9be630d7b5..6ad2c687dd 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 df200b48a7..f5bd48f17e 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 a8f1dbc3aa..8604b3b7d9 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 4b3623d412..0d67029de4 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 33693c990b..13283fe662 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 bfc4cb51ef..75a7a5d1be 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 2ae3ce5f6a..aff1464ddd 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") { From d77ab2a14ca31e3d77b0e264563419d1d6761bbe Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Thu, 16 Aug 2018 09:28:13 +0200 Subject: [PATCH 2/9] Add TraverseEmpty instance for Chain --- core/src/main/scala/cats/data/Chain.scala | 23 +++++++++++++++++++ .../test/scala/cats/tests/ChainSuite.scala | 5 +++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 99c95eaa89..577150810c 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -617,6 +617,29 @@ private[data] sealed abstract class ChainInstances extends ChainInstances1 { } } + implicit val catsDataTraverseEmptyForChain: TraverseEmpty[Chain] = new TraverseEmpty[Chain] { + def traverse: Traverse[Chain] = catsDataInstancesForChain + + override def filter[A](fa: Chain[A])(f: A => Boolean): Chain[A] = fa.filter(f) + + override def collect[A, B](fa: Chain[A])(f: PartialFunction[A, B]): Chain[B] = fa.collect(f) + + override def mapFilter[A, B](fa: Chain[A])(f: A => Option[B]): Chain[B] = fa.collect(Function.unlift(f)) + + override def flattenOption[A](fa: Chain[Option[A]]): Chain[A] = fa.collect { case Some(a) => a } + + def traverseFilter[G[_], A, B](fa: Chain[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[Chain[B]] = + fa.foldRight(G.pure(Chain.empty[B]))( + (a, gcb) => G.map2(f(a), gcb)((ob, cb) => ob.fold(cb)(_ +: cb)) + ) + + override def filterA[G[_], A](fa: Chain[A])(f: A => G[Boolean])(implicit G: Applicative[G]): G[Chain[A]] = + fa.foldRight(G.pure(Chain.empty[A]))( + (a, gca) => + G.map2(f(a), gca)((b, chain) => if (b) a +: chain else chain)) + + } + } private[data] sealed abstract class ChainInstances1 extends ChainInstances2 { diff --git a/tests/src/test/scala/cats/tests/ChainSuite.scala b/tests/src/test/scala/cats/tests/ChainSuite.scala index a83a3d77d1..05eb64b4fc 100644 --- a/tests/src/test/scala/cats/tests/ChainSuite.scala +++ b/tests/src/test/scala/cats/tests/ChainSuite.scala @@ -3,7 +3,7 @@ package tests import cats.data.Chain import cats.kernel.laws.discipline.{MonoidTests, OrderTests} -import cats.laws.discipline.{AlternativeTests, CoflatMapTests, MonadTests, SerializableTests, TraverseTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, MonadTests, SerializableTests, TraverseEmptyTests, TraverseTests} import cats.laws.discipline.arbitrary._ class ChainSuite extends CatsSuite { @@ -25,6 +25,9 @@ class ChainSuite extends CatsSuite { checkAll("Chain[Int]", OrderTests[Chain[Int]].order) checkAll("Order[Chain]", SerializableTests.serializable(Order[Chain[Int]])) + checkAll("Chain[Int]", TraverseEmptyTests[Chain].traverseEmpty[Int, Int, Int]) + checkAll("TraverseEmpty[Chain]", SerializableTests.serializable(TraverseEmpty[Chain])) + test("show"){ Show[Chain[Int]].show(Chain(1, 2, 3)) should === ("Chain(1, 2, 3)") Chain.empty[Int].show should === ("Chain()") From 218c4c9ca2854c75925355f7832d5e670248295a Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 16 Aug 2018 07:39:05 -0700 Subject: [PATCH 3/9] Make TraverseEmpty[Chain] serializable I think that the issue here is that it was referencing an instance-level `catsDataInstancesForChain` from an abstract class. By changing it to reference `Chain.catsDataInstancesForChain`, it is a reference to a static member (and therefore doesn't actually need to be serialized). Take my explanation with a grain of salt -- like everyone else on the planet, I don't actually understand Java serialization. But at the end of the day it works :) --- core/src/main/scala/cats/data/Chain.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 577150810c..7957cba940 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -618,7 +618,7 @@ private[data] sealed abstract class ChainInstances extends ChainInstances1 { } implicit val catsDataTraverseEmptyForChain: TraverseEmpty[Chain] = new TraverseEmpty[Chain] { - def traverse: Traverse[Chain] = catsDataInstancesForChain + def traverse: Traverse[Chain] = Chain.catsDataInstancesForChain override def filter[A](fa: Chain[A])(f: A => Boolean): Chain[A] = fa.filter(f) From a3f0d262f18abab0d90aaa1fe7005c1fcf62a1a7 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Fri, 17 Aug 2018 09:51:55 +0200 Subject: [PATCH 4/9] Remove conversion to mainline classes --- core/src/main/scala/cats/FunctorEmpty.scala | 6 ------ core/src/main/scala/cats/TraverseEmpty.scala | 5 ----- core/src/main/scala/cats/data/EitherT.scala | 2 +- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/core/src/main/scala/cats/FunctorEmpty.scala b/core/src/main/scala/cats/FunctorEmpty.scala index 4d1fa6d6cc..21c75a119e 100644 --- a/core/src/main/scala/cats/FunctorEmpty.scala +++ b/core/src/main/scala/cats/FunctorEmpty.scala @@ -20,9 +20,3 @@ trait FunctorEmpty[F[_]] extends Serializable { 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 index be0eb8f162..03bf872ef8 100644 --- a/core/src/main/scala/cats/TraverseEmpty.scala +++ b/core/src/main/scala/cats/TraverseEmpty.scala @@ -24,8 +24,3 @@ trait TraverseEmpty[F[_]] extends FunctorEmpty[F] { 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/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index f644677753..a35201c2b6 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -735,7 +735,7 @@ private[data] sealed trait EitherTFunctorEmpty[F[_], E] extends FunctorEmpty[Eit } 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]))) + EitherT[F, E, A](F.flattenOption[Either[E, A]](F.functor.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] = { From d323da25ddc33611fb9b777d5fb31a27734e9a10 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 28 Aug 2018 14:59:28 +0200 Subject: [PATCH 5/9] Add traverseFilter <-> traverse consistency law --- laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala | 8 ++++++-- .../scala/cats/laws/discipline/TraverseEmptyTests.scala | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala b/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala index 06a9e2e847..5c98a459cb 100644 --- a/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala @@ -8,10 +8,14 @@ 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]]] = { + def traverseFilterIdentity[G[_]: Applicative, A](fa: F[A]): IsEq[G[F[A]]] = { fa.traverseFilter(_.some.pure[G]) <-> fa.pure[G] } + def traverseFilterConsistentWithTraverse[G[_]: Applicative, A](fa: F[A], f: A => G[A]): IsEq[G[F[A]]] = { + fa.traverseFilter(a => f(a).map(_.some)) <-> F.traverse.traverse(fa)(f) + } + def traverseFilterComposition[A, B, C, M[_], N[_]](fa: F[A], f: A => M[Option[B]], g: B => N[Option[C]] @@ -26,7 +30,7 @@ trait TraverseEmptyLaws[F[_]] extends FunctorEmptyLaws[F] { lhs <-> rhs } - def filterAConsistentWithTraverseFilter[G[_] : Applicative, A](fa: F[A], f: A => G[Boolean]): IsEq[G[F[A]]] = { + 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)) } } diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala index 985858e80a..d163b444ed 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala @@ -15,6 +15,7 @@ trait TraverseEmptyTests[F[_]] extends FunctorEmptyTests[F] { ArbFOA: Arbitrary[F[Option[A]]], ArbFABoo: Arbitrary[PartialFunction[A, B]], ArbAOB: Arbitrary[A => Option[B]], + ArbAOA: Arbitrary[A => Option[A]], ArbAOOB: Arbitrary[A => Option[Option[B]]], ArbBOC: Arbitrary[B => Option[C]], ArbBOOC: Arbitrary[B => Option[Option[C]]], @@ -32,6 +33,7 @@ trait TraverseEmptyTests[F[_]] extends FunctorEmptyTests[F] { parent = Some(functorEmpty[A, B, C]), "traverseFilter identity" -> forAll(laws.traverseFilterIdentity[Option, A] _), "traverseFilter nested composition" -> forAll(laws.traverseFilterComposition[A, B, C, Option, Option] _), + "traverseFilter consistent with traverse" -> forAll(laws.traverseFilterConsistentWithTraverse[Option, A] _), "filterA consistent with traverseFilter" -> forAll(laws.filterAConsistentWithTraverseFilter[Option, A] _) ) } From 8255d59b97a8307cf7948563a8459e6ab720ed5a Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 28 Aug 2018 17:47:07 +0200 Subject: [PATCH 6/9] Make Functor override final --- core/src/main/scala/cats/TraverseEmpty.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/TraverseEmpty.scala b/core/src/main/scala/cats/TraverseEmpty.scala index 03bf872ef8..5dc3bd631f 100644 --- a/core/src/main/scala/cats/TraverseEmpty.scala +++ b/core/src/main/scala/cats/TraverseEmpty.scala @@ -14,7 +14,7 @@ import simulacrum.typeclass trait TraverseEmpty[F[_]] extends FunctorEmpty[F] { def traverse: Traverse[F] - override def functor: Functor[F] = traverse + final 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]] From bf07dd36807a4a014a94af3c67c7b233b149d6bd Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Tue, 28 Aug 2018 18:00:28 +0200 Subject: [PATCH 7/9] Rename Functor and Traverse Empty to Filter --- ...FunctorEmpty.scala => FunctorFilter.scala} | 4 ++-- ...averseEmpty.scala => TraverseFilter.scala} | 4 ++-- core/src/main/scala/cats/data/Chain.scala | 2 +- core/src/main/scala/cats/data/Const.scala | 2 +- core/src/main/scala/cats/data/EitherT.scala | 14 ++++++------- core/src/main/scala/cats/data/Nested.scala | 10 +++++----- core/src/main/scala/cats/data/OptionT.scala | 4 ++-- core/src/main/scala/cats/instances/list.scala | 2 +- core/src/main/scala/cats/instances/map.scala | 4 ++-- .../main/scala/cats/instances/option.scala | 2 +- .../main/scala/cats/instances/sortedMap.scala | 6 +++--- .../main/scala/cats/instances/stream.scala | 2 +- .../main/scala/cats/instances/vector.scala | 2 +- core/src/main/scala/cats/syntax/all.scala | 4 ++-- .../main/scala/cats/syntax/functorEmpty.scala | 4 ---- .../scala/cats/syntax/functorFilter.scala | 4 ++++ core/src/main/scala/cats/syntax/package.scala | 4 ++-- .../scala/cats/syntax/traverseEmpty.scala | 4 ---- .../scala/cats/syntax/traverseFilter.scala | 4 ++++ ...mptyLaws.scala => FunctorFilterLaws.scala} | 10 +++++----- ...ptyLaws.scala => TraverseFilterLaws.scala} | 10 +++++----- ...tyTests.scala => FunctorFilterTests.scala} | 14 ++++++------- ...yTests.scala => TraverseFilterTests.scala} | 16 +++++++-------- .../test/scala/cats/tests/ChainSuite.scala | 6 +++--- .../test/scala/cats/tests/ConstSuite.scala | 4 ++-- .../test/scala/cats/tests/EitherTSuite.scala | 20 +++++++++---------- .../src/test/scala/cats/tests/ListSuite.scala | 6 +++--- .../test/scala/cats/tests/ListWrapper.scala | 12 +++++------ .../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 | 6 +++--- .../test/scala/cats/tests/StreamSuite.scala | 6 +++--- .../test/scala/cats/tests/VectorSuite.scala | 6 +++--- 35 files changed, 110 insertions(+), 110 deletions(-) rename core/src/main/scala/cats/{FunctorEmpty.scala => FunctorFilter.scala} (76%) rename core/src/main/scala/cats/{TraverseEmpty.scala => TraverseFilter.scala} (84%) delete mode 100644 core/src/main/scala/cats/syntax/functorEmpty.scala create mode 100644 core/src/main/scala/cats/syntax/functorFilter.scala delete mode 100644 core/src/main/scala/cats/syntax/traverseEmpty.scala create mode 100644 core/src/main/scala/cats/syntax/traverseFilter.scala rename laws/src/main/scala/cats/laws/{FunctorEmptyLaws.scala => FunctorFilterLaws.scala} (80%) rename laws/src/main/scala/cats/laws/{TraverseEmptyLaws.scala => TraverseFilterLaws.scala} (83%) rename laws/src/main/scala/cats/laws/discipline/{FunctorEmptyTests.scala => FunctorFilterTests.scala} (79%) rename laws/src/main/scala/cats/laws/discipline/{TraverseEmptyTests.scala => TraverseFilterTests.scala} (80%) diff --git a/core/src/main/scala/cats/FunctorEmpty.scala b/core/src/main/scala/cats/FunctorFilter.scala similarity index 76% rename from core/src/main/scala/cats/FunctorEmpty.scala rename to core/src/main/scala/cats/FunctorFilter.scala index 21c75a119e..90a43c0e64 100644 --- a/core/src/main/scala/cats/FunctorEmpty.scala +++ b/core/src/main/scala/cats/FunctorFilter.scala @@ -3,10 +3,10 @@ package cats import simulacrum.typeclass /** - * `FunctorEmpty[F]` allows you to `map` and filter out elements simultaneously. + * `FunctorFilter[F]` allows you to `map` and filter out elements simultaneously. */ @typeclass -trait FunctorEmpty[F[_]] extends Serializable { +trait FunctorFilter[F[_]] extends Serializable { def functor: Functor[F] def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] diff --git a/core/src/main/scala/cats/TraverseEmpty.scala b/core/src/main/scala/cats/TraverseFilter.scala similarity index 84% rename from core/src/main/scala/cats/TraverseEmpty.scala rename to core/src/main/scala/cats/TraverseFilter.scala index 5dc3bd631f..ad15ee7311 100644 --- a/core/src/main/scala/cats/TraverseEmpty.scala +++ b/core/src/main/scala/cats/TraverseFilter.scala @@ -3,7 +3,7 @@ package cats import simulacrum.typeclass /** - * `TraverseEmpty`, also known as `Witherable`, represents list-like structures + * `TraverseFilter`, also known as `Witherable`, represents list-like structures * that can essentially have a `traverse` and a `filter` applied as a single * combined operation (`traverseFilter`). * @@ -11,7 +11,7 @@ import simulacrum.typeclass */ @typeclass -trait TraverseEmpty[F[_]] extends FunctorEmpty[F] { +trait TraverseFilter[F[_]] extends FunctorFilter[F] { def traverse: Traverse[F] final override def functor: Functor[F] = traverse diff --git a/core/src/main/scala/cats/data/Chain.scala b/core/src/main/scala/cats/data/Chain.scala index 96575bcf8a..e577de3e53 100644 --- a/core/src/main/scala/cats/data/Chain.scala +++ b/core/src/main/scala/cats/data/Chain.scala @@ -617,7 +617,7 @@ private[data] sealed abstract class ChainInstances extends ChainInstances1 { } } - implicit val catsDataTraverseEmptyForChain: TraverseEmpty[Chain] = new TraverseEmpty[Chain] { + implicit val catsDataTraverseFilterForChain: TraverseFilter[Chain] = new TraverseFilter[Chain] { def traverse: Traverse[Chain] = Chain.catsDataInstancesForChain override def filter[A](fa: Chain[A])(f: A => Boolean): Chain[A] = fa.filter(f) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index e8beb676d8..9532cc7440 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -87,7 +87,7 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { fa.traverse(f) } - implicit def catsDataTraverseEmptyForConst[C]: TraverseEmpty[Const[C, ?]] = new TraverseEmpty[Const[C, ?]] { + implicit def catsDataTraverseFilterForConst[C]: TraverseFilter[Const[C, ?]] = new TraverseFilter[Const[C, ?]] { override def mapFilter[A, B](fa: Const[C, A])(f: (A) => Option[B]): Const[C, B] = fa.retag diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index a35201c2b6..583ad07089 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -507,9 +507,9 @@ private[data] abstract class EitherTInstances extends EitherTInstances1 { 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 + implicit def catsDataTraverseFilterForEitherT[F[_], L](implicit F0: TraverseFilter[F]): TraverseFilter[EitherT[F, L, ?]] = + new EitherTFunctorFilter[F, L] with TraverseFilter[EitherT[F, L, ?]] { + implicit def F: FunctorFilter[F] = F0 def traverse: Traverse[EitherT[F, L, ?]] = catsDataTraverseForEitherT[F, L](F0.traverse) def traverseFilter[G[_], A, B] @@ -560,8 +560,8 @@ private[data] abstract class EitherTInstances1 extends EitherTInstances2 { 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 } + implicit def catsDataFunctorFilterForEitherT[F[_], L](implicit F0: FunctorFilter[F]): FunctorFilter[EitherT[F, L, ?]] = + new EitherTFunctorFilter[F, L] { implicit def F = F0 } } private[data] abstract class EitherTInstances2 extends EitherTInstances3 { @@ -722,8 +722,8 @@ 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] +private[data] sealed trait EitherTFunctorFilter[F[_], E] extends FunctorFilter[EitherT[F, E, ?]] { + implicit def F: FunctorFilter[F] override def functor: Functor[EitherT[F, E, ?]] = EitherT.catsDataFunctorForEitherT[F, E](F.functor) diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index efc8f520ec..871c7e90fd 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -54,10 +54,10 @@ private[data] sealed abstract class NestedInstances extends NestedInstances0 { 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 def catsDataTraverseFilterForNested[F[_], G[_]](implicit F0: Traverse[F], G0: TraverseFilter[G]): TraverseFilter[Nested[F, G, ?]] = + new NestedTraverseFilter[F, G] { implicit val F: Traverse[F] = F0 - implicit val G: TraverseEmpty[G] = G0 + implicit val G: TraverseFilter[G] = G0 } @@ -323,10 +323,10 @@ private[data] trait NestedInvariantSemigroupalApply[F[_], G[_]] extends Invarian Nested(FG.product(fa.value, fb.value)) } -private[data] abstract class NestedTraverseEmpty[F[_], G[_]] extends TraverseEmpty[Nested[F, G, ?]] { +private[data] abstract class NestedTraverseFilter[F[_], G[_]] extends TraverseFilter[Nested[F, G, ?]] { implicit val F: Traverse[F] - implicit val G: TraverseEmpty[G] + implicit val G: TraverseFilter[G] def traverse: Traverse[Nested[F, G, ?]] = Nested.catsDataTraverseForNested(F, G.traverse) diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 9552ee855d..9e057eec66 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -235,8 +235,8 @@ private[data] sealed abstract class OptionTInstances extends OptionTInstances0 { OptionT(F.defer(fa.value)) } - implicit def optionTFunctorEmpty[F[_]: Functor]: FunctorEmpty[OptionT[F, ?]] = { - new FunctorEmpty[OptionT[F, ?]] { + implicit def optionTFunctorFilter[F[_]: Functor]: FunctorFilter[OptionT[F, ?]] = { + new FunctorFilter[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) diff --git a/core/src/main/scala/cats/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index 90761bfa27..7ef2972a0b 100644 --- a/core/src/main/scala/cats/instances/list.scala +++ b/core/src/main/scala/cats/instances/list.scala @@ -145,7 +145,7 @@ trait ListInstances extends cats.kernel.instances.ListInstances { } trait ListInstancesBinCompat0 { - implicit val catsStdTraverseEmptyForList: TraverseEmpty[List] = new TraverseEmpty[List] { + implicit val catsStdTraverseFilterForList: TraverseFilter[List] = new TraverseFilter[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)) diff --git a/core/src/main/scala/cats/instances/map.scala b/core/src/main/scala/cats/instances/map.scala index 745caa60e4..fda0d30052 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -109,8 +109,8 @@ trait MapInstancesBinCompat0 { } } - implicit def catsStdFunctorEmptyForMap[K]: FunctorEmpty[Map[K, ?]] = { - new FunctorEmpty[Map[K, ?]] { + implicit def catsStdFunctorFilterForMap[K]: FunctorFilter[Map[K, ?]] = { + new FunctorFilter[Map[K, ?]] { val functor: Functor[Map[K, ?]] = cats.instances.map.catsStdInstancesForMap[K] diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index a03e004b64..16d72006b0 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -123,7 +123,7 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances { } trait OptionInstancesBinCompat0 { - implicit val catsStdTraverseEmptyForOption: TraverseEmpty[Option] = new TraverseEmpty[Option] { + implicit val catsStdTraverseFilterForOption: TraverseFilter[Option] = new TraverseFilter[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) diff --git a/core/src/main/scala/cats/instances/sortedMap.scala b/core/src/main/scala/cats/instances/sortedMap.scala index 708841d149..a23a18f150 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, Order, Show, Traverse, TraverseEmpty} +import cats.{Always, Applicative, Eval, FlatMap, Foldable, Monoid, Order, Show, Traverse, TraverseFilter} import cats.kernel._ import cats.kernel.instances.StaticMethods @@ -168,8 +168,8 @@ 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 def catsStdTraverseFilterForSortedMap[K: Order]: TraverseFilter[SortedMap[K, ?]] = + new TraverseFilter[SortedMap[K, ?]] { implicit val ordering: Ordering[K] = Order[K].toOrdering diff --git a/core/src/main/scala/cats/instances/stream.scala b/core/src/main/scala/cats/instances/stream.scala index c33730c315..1d97c90fb0 100644 --- a/core/src/main/scala/cats/instances/stream.scala +++ b/core/src/main/scala/cats/instances/stream.scala @@ -155,7 +155,7 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances { } trait StreamInstancesBinCompat0 { - implicit val catsStdTraverseEmptyForStream: TraverseEmpty[Stream] = new TraverseEmpty[Stream] { + implicit val catsStdTraverseFilterForStream: TraverseFilter[Stream] = new TraverseFilter[Stream] { val traverse: Traverse[Stream] = cats.instances.stream.catsStdInstancesForStream override def mapFilter[A, B](fa: Stream[A])(f: (A) => Option[B]): Stream[B] = { diff --git a/core/src/main/scala/cats/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index e2bafc1237..1b1ddec740 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -116,7 +116,7 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { } trait VectorInstancesBinCompat0 { - implicit val catsStdTraverseEmptyForVector: TraverseEmpty[Vector] = new TraverseEmpty[Vector] { + implicit val catsStdTraverseFilterForVector: TraverseFilter[Vector] = new TraverseFilter[Vector] { val traverse: Traverse[Vector] = cats.instances.vector.catsStdInstancesForVector override def mapFilter[A, B](fa: Vector[A])(f: (A) => Option[B]): Vector[B] = diff --git a/core/src/main/scala/cats/syntax/all.scala b/core/src/main/scala/cats/syntax/all.scala index 92d7f2b59d..d131c8bd25 100644 --- a/core/src/main/scala/cats/syntax/all.scala +++ b/core/src/main/scala/cats/syntax/all.scala @@ -72,5 +72,5 @@ trait AllSyntaxBinCompat1 trait AllSyntaxBinCompat2 extends ParallelTraverseSyntax - with TraverseEmptySyntax - with FunctorEmptySyntax + with TraverseFilterSyntax + with FunctorFilterSyntax diff --git a/core/src/main/scala/cats/syntax/functorEmpty.scala b/core/src/main/scala/cats/syntax/functorEmpty.scala deleted file mode 100644 index ee687de18f..0000000000 --- a/core/src/main/scala/cats/syntax/functorEmpty.scala +++ /dev/null @@ -1,4 +0,0 @@ -package cats -package syntax - -trait FunctorEmptySyntax extends FunctorEmpty.ToFunctorEmptyOps diff --git a/core/src/main/scala/cats/syntax/functorFilter.scala b/core/src/main/scala/cats/syntax/functorFilter.scala new file mode 100644 index 0000000000..a80b6c2618 --- /dev/null +++ b/core/src/main/scala/cats/syntax/functorFilter.scala @@ -0,0 +1,4 @@ +package cats +package syntax + +trait FunctorFilterSyntax extends FunctorFilter.ToFunctorFilterOps diff --git a/core/src/main/scala/cats/syntax/package.scala b/core/src/main/scala/cats/syntax/package.scala index dd6a5fbf56..c336eecd13 100644 --- a/core/src/main/scala/cats/syntax/package.scala +++ b/core/src/main/scala/cats/syntax/package.scala @@ -28,7 +28,7 @@ package object syntax { object flatMap extends FlatMapSyntax object foldable extends FoldableSyntax object functor extends FunctorSyntax - object functorEmpty extends FunctorEmptySyntax + object functorFilter extends FunctorFilterSyntax object group extends GroupSyntax object invariant extends InvariantSyntax object ior extends IorSyntax @@ -50,7 +50,7 @@ package object syntax { object show extends ShowSyntax object strong extends StrongSyntax object traverse extends TraverseSyntax - object traverseEmpty extends TraverseEmptySyntax + object traverseFilter extends TraverseFilterSyntax 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 deleted file mode 100644 index bfe49e712f..0000000000 --- a/core/src/main/scala/cats/syntax/traverseEmpty.scala +++ /dev/null @@ -1,4 +0,0 @@ -package cats -package syntax - -trait TraverseEmptySyntax extends TraverseEmpty.ToTraverseEmptyOps diff --git a/core/src/main/scala/cats/syntax/traverseFilter.scala b/core/src/main/scala/cats/syntax/traverseFilter.scala new file mode 100644 index 0000000000..794caeddee --- /dev/null +++ b/core/src/main/scala/cats/syntax/traverseFilter.scala @@ -0,0 +1,4 @@ +package cats +package syntax + +trait TraverseFilterSyntax extends TraverseFilter.ToTraverseFilterOps diff --git a/laws/src/main/scala/cats/laws/FunctorEmptyLaws.scala b/laws/src/main/scala/cats/laws/FunctorFilterLaws.scala similarity index 80% rename from laws/src/main/scala/cats/laws/FunctorEmptyLaws.scala rename to laws/src/main/scala/cats/laws/FunctorFilterLaws.scala index 814580896c..32af361b1d 100644 --- a/laws/src/main/scala/cats/laws/FunctorEmptyLaws.scala +++ b/laws/src/main/scala/cats/laws/FunctorFilterLaws.scala @@ -2,8 +2,8 @@ package cats package laws -trait FunctorEmptyLaws[F[_]] { - implicit def F: FunctorEmpty[F] +trait FunctorFilterLaws[F[_]] { + implicit def F: FunctorFilter[F] implicit def functor: Functor[F] = F.functor @@ -32,7 +32,7 @@ trait FunctorEmptyLaws[F[_]] { } } -object FunctorEmptyLaws { - def apply[F[_]](implicit ev: FunctorEmpty[F]): FunctorEmptyLaws[F] = - new FunctorEmptyLaws[F] { def F: FunctorEmpty[F] = ev } +object FunctorFilterLaws { + def apply[F[_]](implicit ev: FunctorFilter[F]): FunctorFilterLaws[F] = + new FunctorFilterLaws[F] { def F: FunctorFilter[F] = ev } } diff --git a/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala b/laws/src/main/scala/cats/laws/TraverseFilterLaws.scala similarity index 83% rename from laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala rename to laws/src/main/scala/cats/laws/TraverseFilterLaws.scala index 5c98a459cb..bd799fa8f6 100644 --- a/laws/src/main/scala/cats/laws/TraverseEmptyLaws.scala +++ b/laws/src/main/scala/cats/laws/TraverseFilterLaws.scala @@ -5,8 +5,8 @@ import cats.data.Nested import cats.syntax.all._ import cats.instances.option._ -trait TraverseEmptyLaws[F[_]] extends FunctorEmptyLaws[F] { - implicit override def F: TraverseEmpty[F] +trait TraverseFilterLaws[F[_]] extends FunctorFilterLaws[F] { + implicit override def F: TraverseFilter[F] def traverseFilterIdentity[G[_]: Applicative, A](fa: F[A]): IsEq[G[F[A]]] = { fa.traverseFilter(_.some.pure[G]) <-> fa.pure[G] @@ -35,7 +35,7 @@ trait TraverseEmptyLaws[F[_]] extends FunctorEmptyLaws[F] { } } -object TraverseEmptyLaws { - def apply[F[_]](implicit ev: TraverseEmpty[F]): TraverseEmptyLaws[F] = - new TraverseEmptyLaws[F] { def F: TraverseEmpty[F] = ev } +object TraverseFilterLaws { + def apply[F[_]](implicit ev: TraverseFilter[F]): TraverseFilterLaws[F] = + new TraverseFilterLaws[F] { def F: TraverseFilter[F] = ev } } diff --git a/laws/src/main/scala/cats/laws/discipline/FunctorEmptyTests.scala b/laws/src/main/scala/cats/laws/discipline/FunctorFilterTests.scala similarity index 79% rename from laws/src/main/scala/cats/laws/discipline/FunctorEmptyTests.scala rename to laws/src/main/scala/cats/laws/discipline/FunctorFilterTests.scala index 38fe5e7a67..01fead48a2 100644 --- a/laws/src/main/scala/cats/laws/discipline/FunctorEmptyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FunctorFilterTests.scala @@ -6,10 +6,10 @@ import org.scalacheck.Prop.forAll import org.scalacheck.Arbitrary import org.typelevel.discipline.Laws -trait FunctorEmptyTests[F[_]] extends Laws { - def laws: FunctorEmptyLaws[F] +trait FunctorFilterTests[F[_]] extends Laws { + def laws: FunctorFilterLaws[F] - def functorEmpty[A, B, C](implicit + def functorFilter[A, B, C](implicit ArbFA: Arbitrary[F[A]], ArbFABoo: Arbitrary[PartialFunction[A, B]], ArbFOA: Arbitrary[F[Option[A]]], @@ -22,7 +22,7 @@ trait FunctorEmptyTests[F[_]] extends Laws { EqFC: Eq[F[C]] ): RuleSet = { new DefaultRuleSet( - name = "functorEmpty", + name = "functorFilter", parent = None, "mapFilter composition" -> forAll(laws.mapFilterComposition[A, B, C] _), "mapFilter map consistency" -> forAll(laws.mapFilterMapConsistency[A, B] _), @@ -33,7 +33,7 @@ trait FunctorEmptyTests[F[_]] extends Laws { } } -object FunctorEmptyTests { - def apply[F[_]: FunctorEmpty]: FunctorEmptyTests[F] = - new FunctorEmptyTests[F] { def laws: FunctorEmptyLaws[F] = FunctorEmptyLaws[F] } +object FunctorFilterTests { + def apply[F[_]: FunctorFilter]: FunctorFilterTests[F] = + new FunctorFilterTests[F] { def laws: FunctorFilterLaws[F] = FunctorFilterLaws[F] } } diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala similarity index 80% rename from laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala rename to laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala index d163b444ed..77dc281eec 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseEmptyTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala @@ -7,10 +7,10 @@ import org.scalacheck.Prop.forAll import org.scalacheck.Arbitrary import cats.instances.option._ -trait TraverseEmptyTests[F[_]] extends FunctorEmptyTests[F] { - def laws: TraverseEmptyLaws[F] +trait TraverseFilterTests[F[_]] extends FunctorFilterTests[F] { + def laws: TraverseFilterLaws[F] - def traverseEmpty[A, B, C](implicit + def traverseFilter[A, B, C](implicit ArbFA: Arbitrary[F[A]], ArbFOA: Arbitrary[F[Option[A]]], ArbFABoo: Arbitrary[PartialFunction[A, B]], @@ -29,8 +29,8 @@ trait TraverseEmptyTests[F[_]] extends FunctorEmptyTests[F] { EqMNFC: Eq[Nested[Option, Option, F[C]]] ): RuleSet = { new DefaultRuleSet( - name = "traverseEmpty", - parent = Some(functorEmpty[A, B, C]), + name = "traverseFilter", + parent = Some(functorFilter[A, B, C]), "traverseFilter identity" -> forAll(laws.traverseFilterIdentity[Option, A] _), "traverseFilter nested composition" -> forAll(laws.traverseFilterComposition[A, B, C, Option, Option] _), "traverseFilter consistent with traverse" -> forAll(laws.traverseFilterConsistentWithTraverse[Option, A] _), @@ -39,7 +39,7 @@ trait TraverseEmptyTests[F[_]] extends FunctorEmptyTests[F] { } } -object TraverseEmptyTests { - def apply[F[_]: TraverseEmpty]: TraverseEmptyTests[F] = - new TraverseEmptyTests[F] { def laws: TraverseEmptyLaws[F] = TraverseEmptyLaws[F] } +object TraverseFilterTests { + def apply[F[_]: TraverseFilter]: TraverseFilterTests[F] = + new TraverseFilterTests[F] { def laws: TraverseFilterLaws[F] = TraverseFilterLaws[F] } } diff --git a/tests/src/test/scala/cats/tests/ChainSuite.scala b/tests/src/test/scala/cats/tests/ChainSuite.scala index 65608886d0..54fed68873 100644 --- a/tests/src/test/scala/cats/tests/ChainSuite.scala +++ b/tests/src/test/scala/cats/tests/ChainSuite.scala @@ -2,7 +2,7 @@ package cats package tests import cats.data.Chain -import cats.laws.discipline.{AlternativeTests, CoflatMapTests, MonadTests, SerializableTests, TraverseEmptyTests, TraverseTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, MonadTests, SerializableTests, TraverseFilterTests, TraverseTests} import cats.kernel.laws.discipline.{EqTests, MonoidTests, OrderTests, PartialOrderTests} import cats.laws.discipline.arbitrary._ @@ -26,8 +26,8 @@ class ChainSuite extends CatsSuite { checkAll("Order[Chain]", SerializableTests.serializable(Order[Chain[Int]])) - checkAll("Chain[Int]", TraverseEmptyTests[Chain].traverseEmpty[Int, Int, Int]) - checkAll("TraverseEmpty[Chain]", SerializableTests.serializable(TraverseEmpty[Chain])) + checkAll("Chain[Int]", TraverseFilterTests[Chain].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[Chain]", SerializableTests.serializable(TraverseFilter[Chain])) { implicit val partialOrder = ListWrapper.partialOrder[Int] diff --git a/tests/src/test/scala/cats/tests/ConstSuite.scala b/tests/src/test/scala/cats/tests/ConstSuite.scala index e1f7756da7..d233f91ac5 100644 --- a/tests/src/test/scala/cats/tests/ConstSuite.scala +++ b/tests/src/test/scala/cats/tests/ConstSuite.scala @@ -21,8 +21,8 @@ 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, ?]].traverseEmpty[Int, Int, Int]) - checkAll("TraverseEmpty[Const[String, ?]]", SerializableTests.serializable(TraverseEmpty[Const[String, ?]])) + checkAll("Const[String, Int]", TraverseFilterTests[Const[String, ?]].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[Const[String, ?]]", SerializableTests.serializable(TraverseFilter[Const[String, ?]])) // Get Apply[Const[C : Semigroup, ?]], not Applicative[Const[C : Monoid, ?]] { diff --git a/tests/src/test/scala/cats/tests/EitherTSuite.scala b/tests/src/test/scala/cats/tests/EitherTSuite.scala index 296453228c..7f32f45666 100644 --- a/tests/src/test/scala/cats/tests/EitherTSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherTSuite.scala @@ -48,24 +48,24 @@ class EitherTSuite extends CatsSuite { } { - //If a TraverseEmpty for F is defined - implicit val F = ListWrapper.traverseEmpty + //If a TraverseFilter for F is defined + implicit val F = ListWrapper.traverseFilter checkAll("EitherT[ListWrapper, Int, ?]", - TraverseEmptyTests[EitherT[ListWrapper, Int, ?]].traverseEmpty[Int, Int, Int]) - checkAll("TraverseEmpty[EitherT[ListWrapper, Int, ?]]", - SerializableTests.serializable(TraverseEmpty[EitherT[ListWrapper, Int, ?]])) + TraverseFilterTests[EitherT[ListWrapper, Int, ?]].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[EitherT[ListWrapper, Int, ?]]", + SerializableTests.serializable(TraverseFilter[EitherT[ListWrapper, Int, ?]])) } { - //If a FunctorEmpty for F is defined - implicit val F = ListWrapper.functorEmpty + //If a functorFilter for F is defined + implicit val F = ListWrapper.functorFilter checkAll("EitherT[ListWrapper, Int, ?]", - FunctorEmptyTests[EitherT[ListWrapper, Int, ?]].functorEmpty[Int, Int, Int]) - checkAll("FunctorEmpty[EitherT[ListWrapper, Int, ?]]", - SerializableTests.serializable(FunctorEmpty[EitherT[ListWrapper, Int, ?]])) + FunctorFilterTests[EitherT[ListWrapper, Int, ?]].functorFilter[Int, Int, Int]) + checkAll("FunctorFilter[EitherT[ListWrapper, Int, ?]]", + SerializableTests.serializable(FunctorFilter[EitherT[ListWrapper, Int, ?]])) } diff --git a/tests/src/test/scala/cats/tests/ListSuite.scala b/tests/src/test/scala/cats/tests/ListSuite.scala index a74cbcc77a..1e8ed70e04 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.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseFilterTests, TraverseTests} import cats.laws.discipline.arbitrary._ class ListSuite extends CatsSuite { @@ -22,8 +22,8 @@ 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("List[Int]", TraverseFilterTests[List].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[List]", SerializableTests.serializable(TraverseFilter[List])) checkAll("ZipList[Int]", CommutativeApplyTests[ZipList].commutativeApply[Int, Int, Int]) diff --git a/tests/src/test/scala/cats/tests/ListWrapper.scala b/tests/src/test/scala/cats/tests/ListWrapper.scala index 2384c64d52..2f081d7735 100644 --- a/tests/src/test/scala/cats/tests/ListWrapper.scala +++ b/tests/src/test/scala/cats/tests/ListWrapper.scala @@ -56,20 +56,20 @@ object ListWrapper { } } - val traverseEmpty: TraverseEmpty[ListWrapper] = { - val F = TraverseEmpty[List] + val traverseFilter: TraverseFilter[ListWrapper] = { + val F = TraverseFilter[List] - new TraverseEmpty[ListWrapper] { + new TraverseFilter[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] + val functorFilter: FunctorFilter[ListWrapper] = { + val F = FunctorFilter[List] - new FunctorEmpty[ListWrapper] { + new FunctorFilter[ListWrapper] { def functor = ListWrapper.functor def mapFilter[A, B](fa: ListWrapper[A])(f: A => Option[B]): ListWrapper[B] = ListWrapper(F.mapFilter(fa.list)(f)) diff --git a/tests/src/test/scala/cats/tests/MapSuite.scala b/tests/src/test/scala/cats/tests/MapSuite.scala index 591c7942a2..ec9144ae17 100644 --- a/tests/src/test/scala/cats/tests/MapSuite.scala +++ b/tests/src/test/scala/cats/tests/MapSuite.scala @@ -2,7 +2,7 @@ package cats package tests -import cats.laws.discipline.{FlatMapTests, FunctorEmptyTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests, ComposeTests} +import cats.laws.discipline.{FlatMapTests, FunctorFilterTests, SemigroupalTests, SerializableTests, UnorderedTraverseTests, ComposeTests} import cats.laws.discipline.arbitrary._ import cats.arrow.Compose @@ -18,8 +18,8 @@ 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, ?]])) + checkAll("Map[Int, Int]", FunctorFilterTests[Map[Int, ?]].functorFilter[Int, Int, Int]) + checkAll("FunctorFilter[Map[Int, ?]]", SerializableTests.serializable(FunctorFilter[Map[Int, ?]])) checkAll("Map[Int, Long]", ComposeTests[Map].compose[Int, Long, String, Double]) checkAll("Compose[Map]", SerializableTests.serializable(Compose[Map])) diff --git a/tests/src/test/scala/cats/tests/NestedSuite.scala b/tests/src/test/scala/cats/tests/NestedSuite.scala index f5bd48f17e..9b1802a932 100644 --- a/tests/src/test/scala/cats/tests/NestedSuite.scala +++ b/tests/src/test/scala/cats/tests/NestedSuite.scala @@ -26,13 +26,13 @@ class NestedSuite extends CatsSuite { } { - // TraverseEmpty composition - implicit val instance = ListWrapper.traverseEmpty + // TraverseFilter composition + implicit val instance = ListWrapper.traverseFilter 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, ?]])) + TraverseFilterTests[Nested[ListWrapper, ListWrapper, ?]].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[Nested[ListWrapper, ListWrapper, ?]]", + SerializableTests.serializable(TraverseFilter[Nested[ListWrapper, ListWrapper, ?]])) } { diff --git a/tests/src/test/scala/cats/tests/OptionSuite.scala b/tests/src/test/scala/cats/tests/OptionSuite.scala index 8604b3b7d9..ae36f20129 100644 --- a/tests/src/test/scala/cats/tests/OptionSuite.scala +++ b/tests/src/test/scala/cats/tests/OptionSuite.scala @@ -21,8 +21,8 @@ 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[Int] with Option", TraverseFilterTests[Option].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[Option]", SerializableTests.serializable(TraverseFilter[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 d1223280be..bf34daa456 100644 --- a/tests/src/test/scala/cats/tests/OptionTSuite.scala +++ b/tests/src/test/scala/cats/tests/OptionTSuite.scala @@ -10,7 +10,7 @@ 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]) + checkAll("OptionT[Eval, ?]", FunctorFilterTests[OptionT[Eval, ?]].functorFilter[Int, Int, Int]) { diff --git a/tests/src/test/scala/cats/tests/SortedMapSuite.scala b/tests/src/test/scala/cats/tests/SortedMapSuite.scala index 13283fe662..5c4af5c6fd 100644 --- a/tests/src/test/scala/cats/tests/SortedMapSuite.scala +++ b/tests/src/test/scala/cats/tests/SortedMapSuite.scala @@ -3,7 +3,7 @@ package tests import cats.kernel.CommutativeMonoid import cats.kernel.laws.discipline.{CommutativeMonoidTests, HashTests, MonoidTests} -import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} +import cats.laws.discipline.{FlatMapTests, SemigroupalTests, SerializableTests, TraverseFilterTests, TraverseTests} import cats.laws.discipline.arbitrary._ import scala.collection.immutable.SortedMap @@ -20,8 +20,8 @@ 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, ?]])) + checkAll("SortedMap[Int, Int]", TraverseFilterTests[SortedMap[Int, ?]].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[SortedMap[Int, ?]]", SerializableTests.serializable(TraverseFilter[SortedMap[Int, ?]])) test("show isn't empty and is formatted as expected") { forAll { (map: SortedMap[Int, String]) => diff --git a/tests/src/test/scala/cats/tests/StreamSuite.scala b/tests/src/test/scala/cats/tests/StreamSuite.scala index 75a7a5d1be..25f7c1bb4d 100644 --- a/tests/src/test/scala/cats/tests/StreamSuite.scala +++ b/tests/src/test/scala/cats/tests/StreamSuite.scala @@ -1,7 +1,7 @@ package cats package tests -import cats.laws.discipline.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseFilterTests, TraverseTests} import cats.data.ZipStream import cats.laws.discipline.arbitrary._ @@ -21,8 +21,8 @@ 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])) + checkAll("Stream[Int]", TraverseFilterTests[Stream].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[Stream]", SerializableTests.serializable(TraverseFilter[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 aff1464ddd..1d9562f8fa 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.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseEmptyTests, TraverseTests} +import cats.laws.discipline.{AlternativeTests, CoflatMapTests, CommutativeApplyTests, MonadTests, SemigroupalTests, SerializableTests, TraverseFilterTests, TraverseTests} import cats.laws.discipline.arbitrary._ class VectorSuite extends CatsSuite { @@ -21,8 +21,8 @@ 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("Vector[Int]", TraverseFilterTests[Vector].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[Vector]", SerializableTests.serializable(TraverseFilter[Vector])) checkAll("ZipVector[Int]", CommutativeApplyTests[ZipVector].commutativeApply[Int, Int, Int]) From 46f2307555c443af45d0bef2674a7a3dbd10e493 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 2 Sep 2018 11:38:07 +0200 Subject: [PATCH 8/9] Add Nested FunctorFilter instance --- core/src/main/scala/cats/data/Nested.scala | 47 ++++++++++++------- .../test/scala/cats/tests/NestedSuite.scala | 10 ++++ 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/core/src/main/scala/cats/data/Nested.scala b/core/src/main/scala/cats/data/Nested.scala index 871c7e90fd..dcc1b800d3 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -68,6 +68,12 @@ private[data] sealed abstract class NestedInstances0 extends NestedInstances1 { new NestedTraverse[F, G] { val FG: Traverse[λ[α => F[G[α]]]] = Traverse[F].compose[G] } + + implicit def catsDataFunctorFilterForNested[F[_], G[_]](implicit F0: Functor[F], G0: FunctorFilter[G]): FunctorFilter[Nested[F, G, ?]] = + new NestedFunctorFilter[F, G] { + implicit val F: Functor[F] = F0 + implicit val G: FunctorFilter[G] = G0 + } } private[data] sealed abstract class NestedInstances1 extends NestedInstances2 { @@ -323,14 +329,14 @@ private[data] trait NestedInvariantSemigroupalApply[F[_], G[_]] extends Invarian Nested(FG.product(fa.value, fb.value)) } -private[data] abstract class NestedTraverseFilter[F[_], G[_]] extends TraverseFilter[Nested[F, G, ?]] { - implicit val F: Traverse[F] +private[data] abstract class NestedFunctorFilter[F[_], G[_]] extends FunctorFilter[Nested[F, G, ?]] { + implicit val F: Functor[F] - implicit val G: TraverseFilter[G] + implicit val G: FunctorFilter[G] - def traverse: Traverse[Nested[F, G, ?]] = Nested.catsDataTraverseForNested(F, G.traverse) + def functor: Functor[Nested[F, G, ?]] = Nested.catsDataFunctorForNested(F, G.functor) - override def mapFilter[A, B](fa: Nested[F, G, A])(f: (A) => Option[B]): Nested[F, G, B] = + 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] = @@ -341,16 +347,25 @@ private[data] abstract class NestedTraverseFilter[F[_], G[_]] extends TraverseFi 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))) +} + +private[data] abstract class NestedTraverseFilter[F[_], G[_]] + extends NestedFunctorFilter[F, G] with TraverseFilter[Nested[F, G, ?]] { + implicit val F: Traverse[F] + + implicit val G: TraverseFilter[G] + + def traverse: Traverse[Nested[F, G, ?]] = Nested.catsDataTraverseForNested(F, G.traverse) + + 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]) - 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]) + 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/tests/src/test/scala/cats/tests/NestedSuite.scala b/tests/src/test/scala/cats/tests/NestedSuite.scala index 9b1802a932..e5b2c25bd2 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, ?]])) } + { + // FunctorFilter composition + implicit val instance = ListWrapper.functorFilter + implicit val functorInstance = ListWrapper.functor + checkAll("Nested[ListWrapper, ListWrapper]", + FunctorFilterTests[Nested[ListWrapper, ListWrapper, ?]].functorFilter[Int, Int, Int]) + checkAll("FunctorFilter[Nested[ListWrapper, ListWrapper, ?]]", + SerializableTests.serializable(FunctorFilter[Nested[ListWrapper, ListWrapper, ?]])) + } + { // TraverseFilter composition implicit val instance = ListWrapper.traverseFilter From b4c5080b35ff1c3e1c3b9d9e9ca77a386a795570 Mon Sep 17 00:00:00 2001 From: Luka Jacobowitz Date: Sun, 2 Sep 2018 20:13:47 +0200 Subject: [PATCH 9/9] Address feedback --- core/src/main/scala/cats/data/EitherT.scala | 47 ------------------- core/src/main/scala/cats/data/Nested.scala | 2 - core/src/main/scala/cats/data/OptionT.scala | 41 ++++++++++++---- .../main/scala/cats/instances/option.scala | 4 +- .../test/scala/cats/tests/EitherTSuite.scala | 22 --------- .../test/scala/cats/tests/OptionTSuite.scala | 23 +++++++++ 6 files changed, 57 insertions(+), 82 deletions(-) diff --git a/core/src/main/scala/cats/data/EitherT.scala b/core/src/main/scala/cats/data/EitherT.scala index 86ba284edd..7dc39d170a 100644 --- a/core/src/main/scala/cats/data/EitherT.scala +++ b/core/src/main/scala/cats/data/EitherT.scala @@ -3,7 +3,6 @@ package data import cats.Bifunctor import cats.instances.either._ -import cats.instances.option._ import cats.syntax.either._ /** @@ -515,28 +514,6 @@ 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 catsDataTraverseFilterForEitherT[F[_], L](implicit F0: TraverseFilter[F]): TraverseFilter[EitherT[F, L, ?]] = - new EitherTFunctorFilter[F, L] with TraverseFilter[EitherT[F, L, ?]] { - implicit def F: FunctorFilter[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 { @@ -568,9 +545,6 @@ 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 catsDataFunctorFilterForEitherT[F[_], L](implicit F0: FunctorFilter[F]): FunctorFilter[EitherT[F, L, ?]] = - new EitherTFunctorFilter[F, L] { implicit def F = F0 } } private[data] abstract class EitherTInstances2 extends EitherTInstances3 { @@ -730,24 +704,3 @@ 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 EitherTFunctorFilter[F[_], E] extends FunctorFilter[EitherT[F, E, ?]] { - implicit def F: FunctorFilter[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.functor.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 dcc1b800d3..e780bd6aa7 100644 --- a/core/src/main/scala/cats/data/Nested.scala +++ b/core/src/main/scala/cats/data/Nested.scala @@ -59,8 +59,6 @@ private[data] sealed abstract class NestedInstances extends NestedInstances0 { implicit val F: Traverse[F] = F0 implicit val G: TraverseFilter[G] = G0 } - - } private[data] sealed abstract class NestedInstances0 extends NestedInstances1 { diff --git a/core/src/main/scala/cats/data/OptionT.scala b/core/src/main/scala/cats/data/OptionT.scala index 9e057eec66..9cac0f5c2a 100644 --- a/core/src/main/scala/cats/data/OptionT.scala +++ b/core/src/main/scala/cats/data/OptionT.scala @@ -1,7 +1,7 @@ package cats package data -import cats.instances.option.{catsStdInstancesForOption => optionInstance} +import cats.instances.option.{catsStdInstancesForOption => optionInstance, catsStdTraverseFilterForOption} import cats.syntax.either._ /** @@ -235,19 +235,25 @@ private[data] sealed abstract class OptionTInstances extends OptionTInstances0 { OptionT(F.defer(fa.value)) } - implicit def optionTFunctorFilter[F[_]: Functor]: FunctorFilter[OptionT[F, ?]] = { - new FunctorFilter[OptionT[F, ?]] { - override val functor: Functor[OptionT[F, ?]] = OptionT.catsDataFunctorForOptionT[F] + implicit def catsDateTraverseFilterForOptionT[F[_]](implicit F0: Traverse[F]): TraverseFilter[OptionT[F, ?]] = + new OptionTFunctorFilter[F] with TraverseFilter[OptionT[F, ?]] { + implicit def F: Functor[F] = F0 - override def mapFilter[A, B](fa: OptionT[F, A])(f: (A) => Option[B]): OptionT[F, B] = fa.subflatMap(f) + val traverse: Traverse[OptionT[F, ?]] = OptionT.catsDataTraverseForOptionT[F] - override def collect[A, B](fa: OptionT[F, A])(f: PartialFunction[A, B]): OptionT[F, B] = fa.subflatMap(f.lift) + def traverseFilter[G[_], A, B](fa: OptionT[F, A]) + (f: A => G[Option[B]]) + (implicit G: Applicative[G]): G[OptionT[F, B]] = + G.map(Traverse[F].traverse[G, Option[A], Option[B]](fa.value) { + oa => TraverseFilter[Option].traverseFilter(oa)(f) + })(OptionT[F, B]) - override def flattenOption[A](fa: OptionT[F, Option[A]]): OptionT[F, A] = fa.subflatMap(identity) + override def filterA[G[_], A](fa: OptionT[F, A]) + (f: A => G[Boolean]) + (implicit G: Applicative[G]): G[OptionT[F, A]] = + G.map(Traverse[F].traverse(fa.value)(TraverseFilter[Option].filterA[G, A](_)(f)))(OptionT[F, A]) - 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 { @@ -265,6 +271,9 @@ private[data] sealed abstract class OptionTInstances0 extends OptionTInstances1 implicit def catsDataPartialOrderForOptionT[F[_], A](implicit F0: PartialOrder[F[Option[A]]]): PartialOrder[OptionT[F, A]] = new OptionTPartialOrder[F, A] { implicit val F = F0 } + + implicit def catsDateFunctorFilterForOptionT[F[_]](implicit F0: Functor[F]): FunctorFilter[OptionT[F, ?]] = + new OptionTFunctorFilter[F] { implicit val F = F0 } } private[data] sealed abstract class OptionTInstances1 extends OptionTInstances2 { @@ -401,6 +410,20 @@ private[data] sealed trait OptionTPartialOrder[F[_], A] extends PartialOrder[Opt override def partialCompare(x: OptionT[F, A], y: OptionT[F, A]): Double = x partialCompare y } +private[data] sealed trait OptionTFunctorFilter[F[_]] extends FunctorFilter[OptionT[F, ?]] { + implicit def F: Functor[F] + + def functor: Functor[OptionT[F, ?]] = OptionT.catsDataFunctorForOptionT[F] + + 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 trait OptionTOrder[F[_], A] extends Order[OptionT[F, A]] with OptionTPartialOrder[F, A]{ override implicit def F: Order[F[Option[A]]] diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index 16d72006b0..0a84a02a65 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -136,13 +136,13 @@ trait OptionInstancesBinCompat0 { 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 None => 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 None => G.pure(Option.empty[A]) case Some(a) => G.map(f(a))(b => if (b) Some(a) else None) } diff --git a/tests/src/test/scala/cats/tests/EitherTSuite.scala b/tests/src/test/scala/cats/tests/EitherTSuite.scala index 883e5115b4..e87400d1c9 100644 --- a/tests/src/test/scala/cats/tests/EitherTSuite.scala +++ b/tests/src/test/scala/cats/tests/EitherTSuite.scala @@ -47,28 +47,6 @@ class EitherTSuite extends CatsSuite { } - { - //If a TraverseFilter for F is defined - implicit val F = ListWrapper.traverseFilter - - checkAll("EitherT[ListWrapper, Int, ?]", - TraverseFilterTests[EitherT[ListWrapper, Int, ?]].traverseFilter[Int, Int, Int]) - checkAll("TraverseFilter[EitherT[ListWrapper, Int, ?]]", - SerializableTests.serializable(TraverseFilter[EitherT[ListWrapper, Int, ?]])) - - } - - { - //If a functorFilter for F is defined - implicit val F = ListWrapper.functorFilter - - checkAll("EitherT[ListWrapper, Int, ?]", - FunctorFilterTests[EitherT[ListWrapper, Int, ?]].functorFilter[Int, Int, Int]) - checkAll("FunctorFilter[EitherT[ListWrapper, Int, ?]]", - SerializableTests.serializable(FunctorFilter[EitherT[ListWrapper, Int, ?]])) - - } - { //if a Monad is defined diff --git a/tests/src/test/scala/cats/tests/OptionTSuite.scala b/tests/src/test/scala/cats/tests/OptionTSuite.scala index bf34daa456..c8df8136dd 100644 --- a/tests/src/test/scala/cats/tests/OptionTSuite.scala +++ b/tests/src/test/scala/cats/tests/OptionTSuite.scala @@ -13,6 +13,29 @@ class OptionTSuite extends CatsSuite { checkAll("OptionT[Eval, ?]", FunctorFilterTests[OptionT[Eval, ?]].functorFilter[Int, Int, Int]) + { + //If a Functor for F is defined + implicit val F = ListWrapper.functor + + checkAll("OptionT[ListWrapper, ?]", + FunctorFilterTests[OptionT[ListWrapper, ?]].functorFilter[Int, Int, Int]) + checkAll("FunctorFilter[OptionT[ListWrapper, ?]]", + SerializableTests.serializable(FunctorFilter[OptionT[ListWrapper, ?]])) + + } + + + { + //If a Traverse for F is defined + implicit val F = ListWrapper.traverse + + checkAll("OptionT[ListWrapper, ?]", + TraverseFilterTests[OptionT[ListWrapper, ?]].traverseFilter[Int, Int, Int]) + checkAll("TraverseFilter[OptionT[ListWrapper, ?]]", + SerializableTests.serializable(TraverseFilter[OptionT[ListWrapper, ?]])) + + } + { implicit val F = ListWrapper.eqv[Option[Int]]