Skip to content

Commit

Permalink
Override Foldable methods (#1532)
Browse files Browse the repository at this point in the history
* Override Foldable methods

- Override Foldable methods in a lot of instances (including
  NonEmptyReducible)
- Override MonoidK algebra to get kernel Monoid instance
- Add Reducible for Tuple2

I might have gone overboard with the "one element foldables" (Option,
Either, ...).

* Improve test coverage overridden Foldable methods

* Add reduceLeft/RightOption consistency laws
  • Loading branch information
peterneyens authored and kailuowang committed Feb 28, 2017
1 parent 93a4bd0 commit a0c6f59
Show file tree
Hide file tree
Showing 22 changed files with 423 additions and 23 deletions.
1 change: 0 additions & 1 deletion core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
68 changes: 67 additions & 1 deletion core/src/main/scala/cats/Reducible.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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))
}

/**
Expand Down Expand Up @@ -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)
}
}
9 changes: 9 additions & 0 deletions core/src/main/scala/cats/data/NonEmptyList.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
16 changes: 15 additions & 1 deletion core/src/main/scala/cats/data/NonEmptyVector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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] =
Expand Down
34 changes: 34 additions & 0 deletions core/src/main/scala/cats/data/Validated.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]] =
Expand Down Expand Up @@ -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 {
Expand Down
36 changes: 36 additions & 0 deletions core/src/main/scala/cats/instances/either.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 13 additions & 0 deletions core/src/main/scala/cats/instances/list.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]] =
Expand Down
34 changes: 34 additions & 0 deletions core/src/main/scala/cats/instances/option.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/instances/set.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]] {
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/instances/stream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]] =
Expand Down
48 changes: 48 additions & 0 deletions core/src/main/scala/cats/instances/try.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) }

Expand All @@ -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

Expand Down
Loading

0 comments on commit a0c6f59

Please sign in to comment.