Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add size to Foldable #1114

Merged
merged 1 commit into from
Jun 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I think that Long was a good choice here. Int is probably the first thing that would have come to my mind, but people could conceivably be using Foldable for some very large structures. Though hopefully if their structure is large enough to pass the max Int value they have overridden size to a more efficient implementation :)


/**
* 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("size is consistent with toList.size") {
forAll { (oa: OneAnd[Vector, Int]) =>
oa.size should === (oa.toList.size.toLong)
}
}

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