Skip to content

Commit

Permalink
Add size to Foldable
Browse files Browse the repository at this point in the history
Override with a more performant implementation in Map, Set, Vector and AndOne
using the size method provided by the standard library.

see #1091
  • Loading branch information
Andrea Fiore committed Jun 13, 2016
1 parent 5cf9da3 commit 015f73e
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 0 deletions.
11 changes: 11 additions & 0 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cats

import scala.collection.mutable
import cats.std.long._
import simulacrum.typeclass

/**
Expand Down Expand Up @@ -56,6 +57,16 @@ import simulacrum.typeclass
}
}

/**
* The size of this Foldable.
*
* This is overriden in structures that have more efficient size implementations
* (e.g. Vector, Set, Map).
*
* Note: will not terminate for infinite-sized collections.
*/
def size[A](fa: F[A]): Long = foldMap(fa)(_ => 1)

/**
* Fold implemented using the given Monoid[A] instance.
*/
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/data/OneAnd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 {
implicit def catsDataReducibleForOneAnd[F[_]](implicit F: Foldable[F]): Reducible[OneAnd[F, ?]] =
new NonEmptyReducible[OneAnd[F,?], F] {
override def split[A](fa: OneAnd[F,A]): (A, F[A]) = (fa.head, fa.tail)

override def size[A](fa: OneAnd[F, A]): Long = 1 + F.size(fa.tail)
}

implicit def catsDataMonadForOneAnd[F[_]](implicit monad: MonadCombine[F]): Monad[OneAnd[F, ?]] =
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/std/map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ trait MapInstances extends cats.kernel.std.MapInstances {
def foldRight[A, B](fa: Map[K, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Foldable.iterateRight(fa.values.iterator, lb)(f)

override def size[A](fa: Map[K, A]): Long = fa.size.toLong

override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty
}
}
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/std/set.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ trait SetInstances extends cats.kernel.std.SetInstances {
def foldRight[A, B](fa: Set[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] =
Foldable.iterateRight(fa.iterator, lb)(f)

override def size[A](fa: Set[A]): Long = fa.size.toLong

override def exists[A](fa: Set[A])(p: A => Boolean): Boolean =
fa.exists(p)

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/scala/cats/std/vector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ trait VectorInstances extends cats.kernel.std.VectorInstances {
Eval.defer(loop(0))
}

override def size[A](fa: Vector[A]): Long = fa.size.toLong

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)(_ +: _)
Expand Down
10 changes: 10 additions & 0 deletions tests/src/test/scala/cats/tests/FoldableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb

def iterator[T](fa: F[T]): Iterator[T]

test("size") {
forAll { (fa: F[Int]) =>
fa.size should === (iterator(fa).size.toLong)
}
}

test("summation") {
forAll { (fa: F[Int]) =>
val total = iterator(fa).sum
Expand Down Expand Up @@ -123,6 +129,10 @@ class FoldableVectorCheck extends FoldableCheck[Vector]("vector") {
def iterator[T](vector: Vector[T]): Iterator[T] = vector.iterator
}

class FoldableSetCheck extends FoldableCheck[Set]("set") {
def iterator[T](set: Set[T]): Iterator[T] = set.iterator
}

class FoldableStreamCheck extends FoldableCheck[Stream]("stream") {
def iterator[T](stream: Stream[T]): Iterator[T] = stream.iterator
}
Expand Down
6 changes: 6 additions & 0 deletions tests/src/test/scala/cats/tests/OneAndTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ class OneAndTests extends CatsSuite {
checkAll("NonEmptyList[Int]", ComonadTests[NonEmptyList].comonad[Int, Int, Int])
checkAll("Comonad[NonEmptyList[A]]", SerializableTests.serializable(Comonad[NonEmptyList]))

test("Foldable[OneAnd[Vector, ?]]#size consistent with Vector#size") {
forAll { (oa: OneAnd[Vector, Int]) =>
oa.size should === (1L + oa.tail.size.toLong)
}
}

test("Show is not empty and is formatted as expected") {
forAll { (nel: NonEmptyList[Int]) =>
nel.show.nonEmpty should === (true)
Expand Down

0 comments on commit 015f73e

Please sign in to comment.