diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 86f386d1e5..46fac29fea 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -314,7 +314,6 @@ import simulacrum.typeclass def foldK[G[_], A](fga: F[G[A]])(implicit G: MonoidK[G]): G[A] = fold(fga)(G.algebra) - /** * Find the first element matching the predicate, if one exists. */ diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index ddf0014c2b..9f3e26684c 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -114,7 +114,7 @@ import simulacrum.typeclass * Traverse `F[A]` using `Apply[G]`. * * `A` values will be mapped into `G[B]` and combined using - * `Applicative#map2`. + * `Apply#map2`. * * This method is similar to [[Foldable.traverse_]]. There are two * main differences: @@ -176,6 +176,16 @@ import simulacrum.typeclass case NonEmptyList(hd, tl) => Reducible[NonEmptyList].reduce(NonEmptyList(hd, a :: intersperseList(tl, a))) } + + override def isEmpty[A](fa: F[A]): Boolean = false + + override def nonEmpty[A](fa: F[A]): Boolean = true + + override def minimumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] = + Some(minimum(fa)) + + override def maximumOption[A](fa: F[A])(implicit A: Order[A]): Option[A] = + Some(maximum(fa)) } /** @@ -210,4 +220,60 @@ abstract class NonEmptyReducible[F[_], G[_]](implicit G: Foldable[G]) extends Re case None => Later(f(a)) } } + + override def size[A](fa: F[A]): Long = { + val (_, tail) = split(fa) + 1 + G.size(tail) + } + + override def fold[A](fa: F[A])(implicit A: Monoid[A]): A = { + val (a, ga) = split(fa) + A.combine(a, G.fold(ga)) + } + + override def foldM[H[_], A, B](fa: F[A], z: B)(f: (B, A) => H[B])(implicit H: Monad[H]): H[B] = { + val (a, ga) = split(fa) + H.flatMap(f(z, a))(G.foldM(ga, _)(f)) + } + + override def find[A](fa: F[A])(f: A => Boolean): Option[A] = { + val (a, ga) = split(fa) + if (f(a)) Some(a) else G.find(ga)(f) + } + + override def exists[A](fa: F[A])(p: A => Boolean): Boolean = { + val (a, ga) = split(fa) + p(a) || G.exists(ga)(p) + } + + override def forall[A](fa: F[A])(p: A => Boolean): Boolean = { + val (a, ga) = split(fa) + p(a) && G.forall(ga)(p) + } + + override def toList[A](fa: F[A]): List[A] = { + val (a, ga) = split(fa) + a :: G.toList(ga) + } + + override def toNonEmptyList[A](fa: F[A]): NonEmptyList[A] = { + val (a, ga) = split(fa) + NonEmptyList(a, G.toList(ga)) + } + + override def filter_[A](fa: F[A])(p: A => Boolean): List[A] = { + val (a, ga) = split(fa) + val filteredTail = G.filter_(ga)(p) + if (p(a)) a :: filteredTail else filteredTail + } + + override def takeWhile_[A](fa: F[A])(p: A => Boolean): List[A] = { + val (a, ga) = split(fa) + if (p(a)) a :: G.takeWhile_(ga)(p) else Nil + } + + override def dropWhile_[A](fa: F[A])(p: A => Boolean): List[A] = { + val (a, ga) = split(fa) + if (p(a)) G.dropWhile_(ga)(p) else a :: G.toList(ga) + } } diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index 3f8da9917e..1031b845cd 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -286,6 +286,15 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 NonEmptyList.fromListUnsafe(buf.result()) } + override def fold[A](fa: NonEmptyList[A])(implicit A: Monoid[A]): A = + fa.reduce + + override def foldM[G[_], A, B](fa: NonEmptyList[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = + Foldable.iteratorFoldM(fa.toList.toIterator, z)(f) + + override def find[A](fa: NonEmptyList[A])(f: A => Boolean): Option[A] = + fa find f + override def forall[A](fa: NonEmptyList[A])(p: A => Boolean): Boolean = fa forall p diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index fbac4f1a1b..73d7e24ea9 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -229,7 +229,6 @@ private[data] sealed trait NonEmptyVectorInstances { def traverse[G[_], A, B](fa: NonEmptyVector[A])(f: (A) => G[B])(implicit G: Applicative[G]): G[NonEmptyVector[B]] = G.map2Eval(f(fa.head), Always(Traverse[Vector].traverse(fa.tail)(f)))(NonEmptyVector(_, _)).value - override def foldLeft[A, B](fa: NonEmptyVector[A], b: B)(f: (B, A) => B): B = fa.foldLeft(b)(f) @@ -251,6 +250,21 @@ private[data] sealed trait NonEmptyVectorInstances { NonEmptyVector.fromVectorUnsafe(buf.result()) } + override def fold[A](fa: NonEmptyVector[A])(implicit A: Monoid[A]): A = + fa.reduce + + override def foldM[G[_], A, B](fa: NonEmptyVector[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = + Foldable.iteratorFoldM(fa.toVector.toIterator, z)(f) + + override def find[A](fa: NonEmptyVector[A])(f: A => Boolean): Option[A] = + fa.find(f) + + override def forall[A](fa: NonEmptyVector[A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def exists[A](fa: NonEmptyVector[A])(p: A => Boolean): Boolean = + fa.exists(p) + override def toList[A](fa: NonEmptyVector[A]): List[A] = fa.toVector.toList override def toNonEmptyList[A](fa: NonEmptyVector[A]): NonEmptyList[A] = diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index c014b2bf35..7d65e570ec 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -294,6 +294,7 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance fab.leftMap(f) } + // scalastyle:off method.length implicit def catsDataInstancesForValidated[E](implicit E: Semigroup[E]): Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] = new Traverse[Validated[E, ?]] with ApplicativeError[Validated[E, ?], E] { def traverse[F[_]: Applicative, A, B](fa: Validated[E, A])(f: A => F[B]): F[Validated[E, B]] = @@ -323,7 +324,40 @@ private[data] sealed abstract class ValidatedInstances extends ValidatedInstance case v @ Validated.Valid(_) => v } def raiseError[A](e: E): Validated[E, A] = Validated.Invalid(e) + + override def reduceLeftToOption[A, B](fa: Validated[E, A])(f: A => B)(g: (B, A) => B): Option[B] = + fa.map(f).toOption + + override def reduceRightToOption[A, B](fa: Validated[E, A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + Now(fa.map(f).toOption) + + override def reduceLeftOption[A](fa: Validated[E, A])(f: (A, A) => A): Option[A] = + fa.toOption + + override def reduceRightOption[A](fa: Validated[E, A])(f: (A, Eval[A]) => Eval[A]): Eval[Option[A]] = + Now(fa.toOption) + + override def size[A](fa: Validated[E, A]): Long = + fa.fold(_ => 0L, _ => 1L) + + override def foldMap[A, B](fa: Validated[E, A])(f: A => B)(implicit B: Monoid[B]): B = + fa.fold(_ => B.empty, f) + + override def find[A](fa: Validated[E, A])(f: A => Boolean): Option[A] = + fa.toOption.filter(f) + + override def exists[A](fa: Validated[E, A])(p: A => Boolean): Boolean = + fa.exists(p) + + override def forall[A](fa: Validated[E, A])(p: A => Boolean): Boolean = + fa.forall(p) + + override def toList[A](fa: Validated[E, A]): List[A] = + fa.fold(_ => Nil, _ :: Nil) + + override def isEmpty[A](fa: Validated[E, A]): Boolean = fa.isInvalid } + // scalastyle:on method.length } private[data] sealed abstract class ValidatedInstances1 extends ValidatedInstances2 { diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index 35d561bb6e..adff14b740 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -83,12 +83,48 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { override def attempt[B](fab: Either[A, B]): Either[A, Either[A, B]] = Right(fab) + override def recover[B](fab: Either[A, B])(pf: PartialFunction[A, B]): Either[A, B] = fab recover pf + override def recoverWith[B](fab: Either[A, B])(pf: PartialFunction[A, Either[A, B]]): Either[A, B] = fab recoverWith pf + override def ensure[B](fab: Either[A, B])(error: => A)(predicate: B => Boolean): Either[A, B] = fab.ensure(error)(predicate) + + override def reduceLeftToOption[B, C](fab: Either[A, B])(f: B => C)(g: (C, B) => C): Option[C] = + fab.right.map(f).toOption + + override def reduceRightToOption[B, C](fab: Either[A, B])(f: B => C)(g: (B, Eval[C]) => Eval[C]): Eval[Option[C]] = + Now(fab.right.map(f).toOption) + + override def reduceLeftOption[B](fab: Either[A, B])(f: (B, B) => B): Option[B] = + fab.right.toOption + + override def reduceRightOption[B](fab: Either[A, B])(f: (B, Eval[B]) => Eval[B]): Eval[Option[B]] = + Now(fab.right.toOption) + + override def size[B](fab: Either[A, B]): Long = + fab.fold(_ => 0L, _ => 1L) + + override def foldMap[B, C](fab: Either[A, B])(f: B => C)(implicit C: Monoid[C]): C = + fab.fold(_ => C.empty, f) + + override def find[B](fab: Either[A, B])(f: B => Boolean): Option[B] = + fab.fold(_ => None, r => if (f(r)) Some(r) else None) + + override def exists[B](fab: Either[A, B])(p: B => Boolean): Boolean = + fab.right.exists(p) + + override def forall[B](fab: Either[A, B])(p: B => Boolean): Boolean = + fab.right.forall(p) + + override def toList[B](fab: Either[A, B]): List[B] = + fab.fold(_ => Nil, _ :: Nil) + + override def isEmpty[B](fab: Either[A, B]): Boolean = + fab.isLeft } // scalastyle:on method.length diff --git a/core/src/main/scala/cats/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index 226b9847b9..5115268bdb 100644 --- a/core/src/main/scala/cats/instances/list.scala +++ b/core/src/main/scala/cats/instances/list.scala @@ -86,6 +86,19 @@ trait ListInstances extends cats.kernel.instances.ListInstances { override def fold[A](fa: List[A])(implicit A: Monoid[A]): A = A.combineAll(fa) override def toList[A](fa: List[A]): List[A] = fa + + override def reduceLeftOption[A](fa: List[A])(f: (A, A) => A): Option[A] = + fa.reduceLeftOption(f) + + override def find[A](fa: List[A])(f: A => Boolean): Option[A] = fa.find(f) + + override def filter_[A](fa: List[A])(p: A => Boolean): List[A] = fa.filter(p) + + override def takeWhile_[A](fa: List[A])(p: A => Boolean): List[A] = fa.takeWhile(p) + + override def dropWhile_[A](fa: List[A])(p: A => Boolean): List[A] = fa.dropWhile(p) + + override def algebra[A]: Monoid[List[A]] = new kernel.instances.ListMonoid[A] } implicit def catsStdShowForList[A:Show]: Show[List[A]] = diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index 3ce0a096f2..6799152fa9 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -71,12 +71,46 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances { override def filter[A](fa: Option[A])(p: A => Boolean): Option[A] = fa.filter(p) + override def reduceLeftToOption[A, B](fa: Option[A])(f: A => B)(g: (B, A) => B): Option[B] = + fa.map(f) + + override def reduceRightToOption[A, B](fa: Option[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + Now(fa.map(f)) + + override def reduceLeftOption[A](fa: Option[A])(f: (A, A) => A): Option[A] = fa + + override def reduceRightOption[A](fa: Option[A])(f: (A, Eval[A]) => Eval[A]): Eval[Option[A]] = + Now(fa) + + override def minimumOption[A](fa: Option[A])(implicit A: Order[A]): Option[A] = fa + + override def maximumOption[A](fa: Option[A])(implicit A: Order[A]): Option[A] = fa + + override def size[A](fa: Option[A]): Long = fa.fold(0L)(_ => 1L) + + override def foldMap[A, B](fa: Option[A])(f: A => B)(implicit B: Monoid[B]): B = + fa.fold(B.empty)(f) + + override def find[A](fa: Option[A])(f: A => Boolean): Option[A] = + fa.filter(f) + override def exists[A](fa: Option[A])(p: A => Boolean): Boolean = fa.exists(p) override def forall[A](fa: Option[A])(p: A => Boolean): Boolean = fa.forall(p) + override def toList[A](fa: Option[A]): List[A] = fa.toList + + override def filter_[A](fa: Option[A])(p: A => Boolean): List[A] = + fa.filter(p).toList + + override def takeWhile_[A](fa: Option[A])(p: A => Boolean): List[A] = + fa.filter(p).toList + + override def dropWhile_[A](fa: Option[A])(p: A => Boolean): List[A] = + fa.filterNot(p).toList + override def isEmpty[A](fa: Option[A]): Boolean = fa.isEmpty } diff --git a/core/src/main/scala/cats/instances/set.scala b/core/src/main/scala/cats/instances/set.scala index 0e07f81ccb..4c2e75421d 100644 --- a/core/src/main/scala/cats/instances/set.scala +++ b/core/src/main/scala/cats/instances/set.scala @@ -34,6 +34,11 @@ trait SetInstances extends cats.kernel.instances.SetInstances { override def fold[A](fa: Set[A])(implicit A: Monoid[A]): A = A.combineAll(fa) override def toList[A](fa: Set[A]): List[A] = fa.toList + + override def reduceLeftOption[A](fa: Set[A])(f: (A, A) => A): Option[A] = + fa.reduceLeftOption(f) + + override def find[A](fa: Set[A])(f: A => Boolean): Option[A] = fa.find(f) } implicit def catsStdShowForSet[A:Show]: Show[Set[A]] = new Show[Set[A]] { diff --git a/core/src/main/scala/cats/instances/stream.scala b/core/src/main/scala/cats/instances/stream.scala index 3a2d4628ef..19a7c71194 100644 --- a/core/src/main/scala/cats/instances/stream.scala +++ b/core/src/main/scala/cats/instances/stream.scala @@ -113,6 +113,13 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances { override def fold[A](fa: Stream[A])(implicit A: Monoid[A]): A = A.combineAll(fa) override def toList[A](fa: Stream[A]): List[A] = fa.toList + + override def reduceLeftOption[A](fa: Stream[A])(f: (A, A) => A): Option[A] = + fa.reduceLeftOption(f) + + override def find[A](fa: Stream[A])(f: A => Boolean): Option[A] = fa.find(f) + + override def algebra[A]: Monoid[Stream[A]] = new kernel.instances.StreamMonoid[A] } implicit def catsStdShowForStream[A: Show]: Show[Stream[A]] = diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index faacee1f57..fe134b9302 100644 --- a/core/src/main/scala/cats/instances/try.scala +++ b/core/src/main/scala/cats/instances/try.scala @@ -63,6 +63,7 @@ trait TryInstances extends TryInstances1 { ta.recoverWith { case t => f(t) } def raiseError[A](e: Throwable): Try[A] = Failure(e) + override def handleError[A](ta: Try[A])(f: Throwable => A): Try[A] = ta.recover { case t => f(t) } @@ -75,6 +76,53 @@ trait TryInstances extends TryInstances1 { override def recoverWith[A](ta: Try[A])(pf: PartialFunction[Throwable, Try[A]]): Try[A] = ta.recoverWith(pf) override def map[A, B](ta: Try[A])(f: A => B): Try[B] = ta.map(f) + + override def reduceLeftToOption[A, B](fa: Try[A])(f: A => B)(g: (B, A) => B): Option[B] = + fa.map(f).toOption + + override def reduceRightToOption[A, B](fa: Try[A])(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + Now(fa.map(f).toOption) + + override def reduceLeftOption[A](fa: Try[A])(f: (A, A) => A): Option[A] = + fa.toOption + + override def reduceRightOption[A](fa: Try[A])(f: (A, Eval[A]) => Eval[A]): Eval[Option[A]] = + Now(fa.toOption) + + override def size[A](fa: Try[A]): Long = + fa match { + case Failure(_) => 0L + case Success(_) => 1L + } + + override def find[A](fa: Try[A])(f: A => Boolean): Option[A] = + fa.toOption.filter(f) + + override def foldMap[A, B](fa: Try[A])(f: A => B)(implicit B: Monoid[B]): B = + fa match { + case Failure(_) => B.empty + case Success(a) => f(a) + } + + override def exists[A](fa: Try[A])(p: A => Boolean): Boolean = + fa match { + case Failure(_) => false + case Success(a) => p(a) + } + + override def forall[A](fa: Try[A])(p: A => Boolean): Boolean = + fa match { + case Failure(_) => true + case Success(a) => p(a) + } + + override def toList[A](fa: Try[A]): List[A] = + fa match { + case Failure(_) => Nil + case Success(a) => a :: Nil + } + + override def isEmpty[A](fa: Try[A]): Boolean = fa.isFailure } // scalastyle:on method.length diff --git a/core/src/main/scala/cats/instances/tuple.scala b/core/src/main/scala/cats/instances/tuple.scala index f4aed0e05b..0e5a8816fa 100644 --- a/core/src/main/scala/cats/instances/tuple.scala +++ b/core/src/main/scala/cats/instances/tuple.scala @@ -24,8 +24,8 @@ sealed trait Tuple2Instances extends Tuple2Instances1 { } } - implicit def catsStdInstancesForTuple2[X]: Traverse[(X, ?)] with Comonad[(X, ?)] = - new Traverse[(X, ?)] with Comonad[(X, ?)] { + implicit def catsStdInstancesForTuple2[X]: Traverse[(X, ?)] with Comonad[(X, ?)] with Reducible[(X, ?)] = + new Traverse[(X, ?)] with Comonad[(X, ?)] with Reducible[(X, ?)] { def traverse[G[_], A, B](fa: (X, A))(f: A => G[B])(implicit G: Applicative[G]): G[(X, B)] = G.map(f(fa._2))((fa._1, _)) @@ -40,6 +40,37 @@ sealed trait Tuple2Instances extends Tuple2Instances1 { def extract[A](fa: (X, A)): A = fa._2 override def coflatten[A](fa: (X, A)): (X, (X, A)) = (fa._1, fa) + + override def foldMap[A, B](fa: (X, A))(f: A => B)(implicit B: Monoid[B]): B = f(fa._2) + + override def reduce[A](fa: (X, A))(implicit A: Semigroup[A]): A = fa._2 + + def reduceLeftTo[A, B](fa: (X, A))(f: A => B)(g: (B, A) => B): B = f(fa._2) + + override def reduceLeft[A](fa: (X, A))(f: (A, A) => A): A = fa._2 + + override def reduceLeftToOption[A, B](fa: (X, A))(f: A => B)(g: (B, A) => B): Option[B] = + Some(f(fa._2)) + + override def reduceRight[A](fa: (X, A))(f: (A, Eval[A]) => Eval[A]): Eval[A] = + Now(fa._2) + + def reduceRightTo[A, B](fa: (X, A))(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[B] = + Now(f(fa._2)) + + override def reduceRightToOption[A, B](fa: (X, A))(f: A => B)(g: (A, Eval[B]) => Eval[B]): Eval[Option[B]] = + Now(Some(f(fa._2))) + + override def reduceMap[A, B](fa: (X, A))(f: A => B)(implicit B: Semigroup[B]): B = + f(fa._2) + + override def size[A](fa: (X, A)): Long = 1L + + override def exists[A](fa: (X, A))(p: A => Boolean): Boolean = p(fa._2) + + override def forall[A](fa: (X, A))(p: A => Boolean): Boolean = p(fa._2) + + override def isEmpty[A](fa: (X, A)): Boolean = false } } diff --git a/core/src/main/scala/cats/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index 70da7b8b0b..827c2f86d8 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -74,9 +74,9 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { override def size[A](fa: Vector[A]): Long = fa.size.toLong override def traverse[G[_], A, B](fa: Vector[A])(f: A => G[B])(implicit G: Applicative[G]): G[Vector[B]] = - foldRight[A, G[Vector[B]]](fa, Always(G.pure(Vector.empty))){ (a, lgvb) => - G.map2Eval(f(a), lgvb)(_ +: _) - }.value + foldRight[A, G[Vector[B]]](fa, Always(G.pure(Vector.empty))){ (a, lgvb) => + G.map2Eval(f(a), lgvb)(_ +: _) + }.value override def exists[A](fa: Vector[A])(p: A => Boolean): Boolean = fa.exists(p) @@ -93,6 +93,13 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { override def fold[A](fa: Vector[A])(implicit A: Monoid[A]): A = A.combineAll(fa) override def toList[A](fa: Vector[A]): List[A] = fa.toList + + override def reduceLeftOption[A](fa: Vector[A])(f: (A, A) => A): Option[A] = + fa.reduceLeftOption(f) + + override def find[A](fa: Vector[A])(f: A => Boolean): Option[A] = fa.find(f) + + override def algebra[A]: Monoid[Vector[A]] = new kernel.instances.VectorMonoid[A] } implicit def catsStdShowForVector[A:Show]: Show[Vector[A]] = diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index fabeb776bc..8fefc3a42b 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -48,6 +48,7 @@ package object cats { f(a, lb) def traverse[G[_], A, B](a: A)(f: A => G[B])(implicit G: Applicative[G]): G[B] = f(a) + override def foldMap[A, B](fa: Id[A])(f: A => B)(implicit B: Monoid[B]): B = f(fa) override def reduce[A](fa: Id[A])(implicit A: Semigroup[A]): A = fa def reduceLeftTo[A, B](fa: Id[A])(f: A => B)(g: (B, A) => B): B = @@ -64,6 +65,7 @@ package object cats { Now(Some(f(fa))) override def reduceMap[A, B](fa: Id[A])(f: A => B)(implicit B: Semigroup[B]): B = f(fa) override def size[A](fa: Id[A]): Long = 1L + override def isEmpty[A](fa: Id[A]): Boolean = false } type Eq[A] = cats.kernel.Eq[A] diff --git a/laws/src/main/scala/cats/laws/FoldableLaws.scala b/laws/src/main/scala/cats/laws/FoldableLaws.scala index 725f57eaa5..6fc378095b 100644 --- a/laws/src/main/scala/cats/laws/FoldableLaws.scala +++ b/laws/src/main/scala/cats/laws/FoldableLaws.scala @@ -74,6 +74,38 @@ trait FoldableLaws[F[_]] { ): Boolean = { !F.isEmpty(fa) || F.forall(fa)(p) } + + /** + * Monadic folding with identity monad is analogous to `foldLeft`. + */ + def foldMIdentity[A, B]( + fa: F[A], + b: B, + f: (B, A) => B + ): IsEq[B] = { + F.foldM[Id, A, B](fa, b)(f) <-> F.foldLeft(fa, b)(f) + } + + /** + * `reduceLeftOption` consistent with `reduceLeftToOption` + */ + def reduceLeftOptionConsistentWithReduceLeftToOption[A]( + fa: F[A], + f: (A, A) => A + ): IsEq[Option[A]] = { + F.reduceLeftOption(fa)(f) <-> F.reduceLeftToOption(fa)(identity)(f) + } + + /** + * `reduceRightOption` consistent with `reduceRightToOption` + */ + def reduceRightOptionConsistentWithReduceRightToOption[A]( + fa: F[A], + f: (A, A) => A + ): IsEq[Option[A]] = { + val g: (A, Eval[A]) => Eval[A] = (a, ea) => ea.map(f(a, _)) + F.reduceRightOption(fa)(g).value <-> F.reduceRightToOption(fa)(identity)(g).value + } } object FoldableLaws { diff --git a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala index a3b5a16f2b..8538d0bc99 100644 --- a/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/FoldableTests.scala @@ -13,7 +13,9 @@ trait FoldableTests[F[_]] extends Laws { ArbFA: Arbitrary[F[A]], B: Monoid[B], CogenA: Cogen[A], - EqB: Eq[B] + CogenB: Cogen[B], + EqB: Eq[B], + EqOptionA: Eq[Option[A]] ): RuleSet = { new DefaultRuleSet( name = "foldable", @@ -24,7 +26,12 @@ trait FoldableTests[F[_]] extends Laws { "forall consistent with exists" -> forAll(laws.forallConsistentWithExists[A] _), "forall true if empty" -> forAll(laws.forallEmpty[A] _), "exists is lazy" -> forAll(laws.existsLazy[A] _), - "forall is lazy" -> forAll(laws.forallLazy[A] _) + "forall is lazy" -> forAll(laws.forallLazy[A] _), + "foldM identity" -> forAll(laws.foldMIdentity[A, B] _), + "reduceLeftOption consistent with reduceLeftToOption" -> + forAll(laws.reduceLeftOptionConsistentWithReduceLeftToOption[A] _), + "reduceRightOption consistent with reduceRightToOption" -> + forAll(laws.reduceRightOptionConsistentWithReduceRightToOption[A] _) ) } } diff --git a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala index b77034f65b..216a567f45 100644 --- a/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/ReducibleTests.scala @@ -17,9 +17,11 @@ trait ReducibleTests[F[_]] extends FoldableTests[F] { ArbFGA: Arbitrary[F[G[A]]], ArbGB: Arbitrary[G[B]], CogenA: Cogen[A], + CogenB: Cogen[B], EqG: Eq[G[Unit]], EqA: Eq[A], EqB: Eq[B], + EqOptionA: Eq[Option[A]], MonoidB: Monoid[B] ): RuleSet = new DefaultRuleSet( diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala index 0ef525483b..0f560535ca 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseFilterTests.scala @@ -18,6 +18,7 @@ trait TraverseFilterTests[F[_]] extends TraverseTests[F] with FunctorFilterTests CogenA: Cogen[A], CogenB: Cogen[B], CogenC: Cogen[C], + CogenM: Cogen[M], M: Monoid[M], EqFA: Eq[F[A]], EqFC: Eq[F[C]], @@ -25,7 +26,8 @@ trait TraverseFilterTests[F[_]] extends TraverseTests[F] with FunctorFilterTests EqXYFC: Eq[X[Y[F[C]]]], EqXFA: Eq[X[F[A]]], EqXFB: Eq[X[F[B]]], - EqYFB: Eq[Y[F[B]]] + EqYFB: Eq[Y[F[B]]], + EqOptionA: Eq[Option[A]] ): RuleSet = { new RuleSet { def name: String = "traverseFilter" diff --git a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala index 447ab090cf..7164707dc9 100644 --- a/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala +++ b/laws/src/main/scala/cats/laws/discipline/TraverseTests.scala @@ -17,13 +17,15 @@ trait TraverseTests[F[_]] extends FunctorTests[F] with FoldableTests[F] { CogenA: Cogen[A], CogenB: Cogen[B], CogenC: Cogen[C], + CogenM: Cogen[M], M: Monoid[M], EqFA: Eq[F[A]], EqFC: Eq[F[C]], EqM: Eq[M], EqXYFC: Eq[X[Y[F[C]]]], EqXFB: Eq[X[F[B]]], - EqYFB: Eq[Y[F[B]]] + EqYFB: Eq[Y[F[B]]], + EqOptionA: Eq[Option[A]] ): RuleSet = { implicit def EqXFBYFB : Eq[(X[F[B]], Y[F[B]])] = new Eq[(X[F[B]], Y[F[B]])] { override def eqv(x: (X[F[B]], Y[F[B]]), y: (X[F[B]], Y[F[B]])): Boolean = diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index f13458beb8..e64254eb6d 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -3,20 +3,23 @@ package tests import org.scalatest.prop.PropertyChecks import org.scalacheck.Arbitrary +import scala.util.Try import cats.instances.all._ +import cats.data.{NonEmptyList, NonEmptyStream, NonEmptyVector, Validated} +import cats.laws.discipline.arbitrary._ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arbitrary[F[Int]], ArbFString: Arbitrary[F[String]]) extends CatsSuite with PropertyChecks { def iterator[T](fa: F[T]): Iterator[T] - test("size") { + test(s"Foldable[$name].size") { forAll { (fa: F[Int]) => fa.size should === (iterator(fa).size.toLong) } } - test("summation") { + test(s"Foldable[$name] summation") { forAll { (fa: F[Int]) => val total = iterator(fa).sum fa.foldLeft(0)(_ + _) should === (total) @@ -26,7 +29,7 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb } } - test("find/exists/forall/filter_/dropWhile_") { + test(s"Foldable[$name].find/exists/forall/filter_/dropWhile_") { forAll { (fa: F[Int], n: Int) => fa.find(_ > n) should === (iterator(fa).find(_ > n)) fa.exists(_ > n) should === (iterator(fa).exists(_ > n)) @@ -37,7 +40,7 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb } } - test("toList/isEmpty/nonEmpty") { + test(s"Foldable[$name].toList/isEmpty/nonEmpty") { forAll { (fa: F[Int]) => fa.toList should === (iterator(fa).toList) fa.isEmpty should === (iterator(fa).isEmpty) @@ -45,7 +48,7 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb } } - test("maximum/minimum") { + test(s"Foldable[$name].maximum/minimum") { forAll { (fa: F[Int]) => val maxOpt = fa.maximumOption val minOpt = fa.minimumOption @@ -60,7 +63,7 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb } } - test("reduceLeftOption/reduceRightOption") { + test(s"Foldable[$name].reduceLeftOption/reduceRightOption") { forAll { (fa: F[Int]) => val list = fa.toList fa.reduceLeftOption(_ - _) should === (list.reduceLeftOption(_ - _)) @@ -68,11 +71,17 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb } } - test("intercalate") { + test(s"Foldable[$name].intercalate") { forAll { (fa: F[String], a: String) => fa.intercalate(a) should === (fa.toList.mkString(a)) } } + + test(s"Foldable[$name].toList") { + forAll { (fa: F[Int]) => + fa.toList should === (iterator(fa).toList) + } + } } class FoldableTestsAdditional extends CatsSuite { @@ -83,7 +92,6 @@ class FoldableTestsAdditional extends CatsSuite { if (a === goal) Now(true) else lb } - test("Foldable[List]") { val F = Foldable[List] @@ -150,6 +158,18 @@ class FoldableTestsAdditional extends CatsSuite { checkFoldMStackSafety[Map[String, ?]](_.map(x => x.toString -> x).toMap) } + test("Foldable[NonEmptyList].foldM stack safety") { + checkFoldMStackSafety[NonEmptyList](xs => NonEmptyList.fromListUnsafe(xs.toList)) + } + + test("Foldable[NonEmptyVector].foldM stack safety") { + checkFoldMStackSafety[NonEmptyVector](xs => NonEmptyVector.fromVectorUnsafe(xs.toVector)) + } + + test("Foldable[NonEmptyStream].foldM stack safety") { + checkFoldMStackSafety[NonEmptyStream](xs => NonEmptyStream(xs.head, xs.tail: _*)) + } + test("Foldable[Stream]") { val F = Foldable[Stream] @@ -199,3 +219,19 @@ class FoldableStreamCheck extends FoldableCheck[Stream]("stream") { class FoldableMapCheck extends FoldableCheck[Map[Int, ?]]("map") { def iterator[T](map: Map[Int, T]): Iterator[T] = map.iterator.map(_._2) } + +class FoldableOptionCheck extends FoldableCheck[Option]("option") { + def iterator[T](option: Option[T]): Iterator[T] = option.iterator +} + +class FoldableEitherCheck extends FoldableCheck[Either[Int, ?]]("either") { + def iterator[T](either: Either[Int, T]): Iterator[T] = either.right.toOption.iterator +} + +class FoldableValidatedCheck extends FoldableCheck[Validated[String, ?]]("validated") { + def iterator[T](validated: Validated[String, T]): Iterator[T] = validated.toOption.iterator +} + +class FoldableTryCheck extends FoldableCheck[Try]("try") { + def iterator[T](tryt: Try[T]): Iterator[T] = tryt.toOption.iterator +} diff --git a/tests/src/test/scala/cats/tests/OneAndTests.scala b/tests/src/test/scala/cats/tests/OneAndTests.scala index a3ec809c2e..f7a12fbe74 100644 --- a/tests/src/test/scala/cats/tests/OneAndTests.scala +++ b/tests/src/test/scala/cats/tests/OneAndTests.scala @@ -3,6 +3,7 @@ package tests import cats.kernel.laws.{GroupLaws, OrderLaws} +import cats.instances.stream._ import cats.data.{NonEmptyStream, OneAnd} import cats.laws.discipline.{ComonadTests, FunctorTests, SemigroupKTests, FoldableTests, MonadTests, SerializableTests, CartesianTests, TraverseTests, ReducibleTests} import cats.laws.discipline.arbitrary._ @@ -174,3 +175,15 @@ class OneAndTests extends CatsSuite { } } } + +class ReducibleNonEmptyStreamCheck extends ReducibleCheck[NonEmptyStream]("NonEmptyStream") { + def iterator[T](nes: NonEmptyStream[T]): Iterator[T] = + (nes.head #:: nes.tail).iterator + + def range(start: Long, endInclusive: Long): NonEmptyStream[Long] = { + // if we inline this we get a bewildering implicit numeric widening + // error message in Scala 2.10 + val tailStart: Long = start + 1L + NonEmptyStream(start, (tailStart).to(endInclusive).toStream) + } +} diff --git a/tests/src/test/scala/cats/tests/TupleTests.scala b/tests/src/test/scala/cats/tests/TupleTests.scala index dc2b281e06..363776e0d5 100644 --- a/tests/src/test/scala/cats/tests/TupleTests.scala +++ b/tests/src/test/scala/cats/tests/TupleTests.scala @@ -3,9 +3,7 @@ package tests import data.NonEmptyList -import cats.laws.discipline.{ - BitraverseTests, ComonadTests, SerializableTests, TraverseTests, MonadTests, FlatMapTests, CartesianTests -} +import cats.laws.discipline._ import cats.laws.discipline.arbitrary._ class TupleTests extends CatsSuite { @@ -29,6 +27,9 @@ class TupleTests extends CatsSuite { checkAll("Monad[(String, ?)]", MonadTests[(String, ?)].monad[Int, Int, String]) checkAll("Monad[(String, ?)] serializable", SerializableTests.serializable(Monad[(String, ?)])) + checkAll("Tuple2[String, Int]", ReducibleTests[(String, ?)].reducible[Option, Int, Int]) + checkAll("Reducible[(String, ?)]", SerializableTests.serializable(Reducible[(String, ?)])) + test("Cartesian composition") { val cart = ContravariantCartesian[Eq].composeFunctor[(Int, ?)] val eq = cart.product(Eq[(Int, String)], Eq[(Int, Int)])