diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 641ef1379d..089634bcdb 100644 --- a/core/src/main/scala/cats/Foldable.scala +++ b/core/src/main/scala/cats/Foldable.scala @@ -1,6 +1,7 @@ package cats import scala.collection.mutable +import cats.instances.either._ import cats.instances.long._ import simulacrum.typeclass @@ -151,6 +152,19 @@ import simulacrum.typeclass */ def size[A](fa: F[A]): Long = foldMap(fa)(_ => 1) + /** + * Get the element at the index of the `Foldable`. + */ + def get[A](fa: F[A])(idx: Long): Option[A] = + if (idx < 0L) None + else + foldM[Either[A, ?], A, Long](fa, 0L) { (i, a) => + if (i == idx) Left(a) else Right(i + 1L) + } match { + case Left(a) => Some(a) + case Right(_) => None + } + /** * Fold implemented using the given Monoid[A] instance. */ diff --git a/core/src/main/scala/cats/Reducible.scala b/core/src/main/scala/cats/Reducible.scala index 9f3e26684c..76ab5d4ca5 100644 --- a/core/src/main/scala/cats/Reducible.scala +++ b/core/src/main/scala/cats/Reducible.scala @@ -226,6 +226,9 @@ abstract class NonEmptyReducible[F[_], G[_]](implicit G: Foldable[G]) extends Re 1 + G.size(tail) } + override def get[A](fa: F[A])(idx: Long): Option[A] = + if (idx == 0L) Some(split(fa)._1) else G.get(split(fa)._2)(idx) + override def fold[A](fa: F[A])(implicit A: Monoid[A]): A = { val (a, ga) = split(fa) A.combine(a, G.fold(ga)) diff --git a/core/src/main/scala/cats/data/Const.scala b/core/src/main/scala/cats/data/Const.scala index 06ca5633f9..6d326a3366 100644 --- a/core/src/main/scala/cats/data/Const.scala +++ b/core/src/main/scala/cats/data/Const.scala @@ -81,6 +81,10 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 { def foldRight[A, B](fa: Const[C, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb + override def size[A](fa: Const[C, A]): Long = 0L + + override def get[A](fa: Const[C, A])(idx: Long): Option[A] = None + override def traverse[G[_]: Applicative, A, B](fa: Const[C, A])(f: A => G[B]): G[Const[C, B]] = fa.traverse(f) } diff --git a/core/src/main/scala/cats/data/EitherK.scala b/core/src/main/scala/cats/data/EitherK.scala index 31b5bbd582..8c6d5e3cc3 100644 --- a/core/src/main/scala/cats/data/EitherK.scala +++ b/core/src/main/scala/cats/data/EitherK.scala @@ -189,6 +189,12 @@ private[data] trait EitherKFoldable[F[_], G[_]] extends Foldable[EitherK[F, G, ? def foldLeft[A, B](fa: EitherK[F, G, A], z: B)(f: (B, A) => B): B = fa.foldLeft(z)(f) + override def size[A](fa: EitherK[F, G, A]): Long = + fa.run.fold(F.size, G.size) + + override def get[A](fa: EitherK[F, G, A])(idx: Long): Option[A] = + fa.run.fold(F.get(_)(idx), G.get(_)(idx)) + override def foldMap[A, B](fa: EitherK[F, G, A])(f: A => B)(implicit M: Monoid[B]): B = fa foldMap f } diff --git a/core/src/main/scala/cats/data/IdT.scala b/core/src/main/scala/cats/data/IdT.scala index e853a7b64e..1005112365 100644 --- a/core/src/main/scala/cats/data/IdT.scala +++ b/core/src/main/scala/cats/data/IdT.scala @@ -63,6 +63,12 @@ private[data] sealed trait IdTFoldable[F[_]] extends Foldable[IdT[F, ?]] { def foldRight[A, B](fa: IdT[F, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa.foldRight(lb)(f) + + override def size[A](fa: IdT[F, A]): Long = + F0.size(fa.value) + + override def get[A](fa: IdT[F, A])(idx: Long): Option[A] = + F0.get(fa.value)(idx) } private[data] sealed trait IdTTraverse[F[_]] extends Traverse[IdT[F, ?]] with IdTFoldable[F] { diff --git a/core/src/main/scala/cats/data/Ior.scala b/core/src/main/scala/cats/data/Ior.scala index 4925462394..5db6cff7a6 100644 --- a/core/src/main/scala/cats/data/Ior.scala +++ b/core/src/main/scala/cats/data/Ior.scala @@ -209,6 +209,11 @@ private[data] sealed abstract class IorInstances0 { def foldRight[B, C](fa: A Ior B, lc: Eval[C])(f: (B, Eval[C]) => Eval[C]): Eval[C] = fa.foldRight(lc)(f) + override def size[B](fa: A Ior B): Long = fa.fold(_ => 0L, _ => 1L, (_, _) => 1L) + + override def get[B](fa: A Ior B)(idx: Long): Option[B] = + if (idx == 0L) fa.toOption else None + override def forall[B](fa: Ior[A, B])(p: (B) => Boolean): Boolean = fa.forall(p) override def exists[B](fa: Ior[A, B])(p: (B) => Boolean): Boolean = fa.exists(p) diff --git a/core/src/main/scala/cats/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index a3c3b13817..d5c7bd17b4 100644 --- a/core/src/main/scala/cats/data/NonEmptyList.scala +++ b/core/src/main/scala/cats/data/NonEmptyList.scala @@ -436,6 +436,9 @@ private[data] sealed trait NonEmptyListInstances extends NonEmptyListInstances0 override def toList[A](fa: NonEmptyList[A]): List[A] = fa.toList override def toNonEmptyList[A](fa: NonEmptyList[A]): NonEmptyList[A] = fa + + override def get[A](fa: NonEmptyList[A])(idx: Long): Option[A] = + if (idx == 0) Some(fa.head) else Foldable[List].get(fa.tail)(idx - 1) } implicit def catsDataShowForNonEmptyList[A](implicit A: Show[A]): Show[NonEmptyList[A]] = diff --git a/core/src/main/scala/cats/data/NonEmptyVector.scala b/core/src/main/scala/cats/data/NonEmptyVector.scala index 73d7e24ea9..ecf79754cc 100644 --- a/core/src/main/scala/cats/data/NonEmptyVector.scala +++ b/core/src/main/scala/cats/data/NonEmptyVector.scala @@ -235,6 +235,9 @@ private[data] sealed trait NonEmptyVectorInstances { override def foldRight[A, B](fa: NonEmptyVector[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = fa.foldRight(lb)(f) + override def get[A](fa: NonEmptyVector[A])(idx: Long): Option[A] = + if (idx < Int.MaxValue) fa.get(idx.toInt) else None + def tailRecM[A, B](a: A)(f: A => NonEmptyVector[Either[A, B]]): NonEmptyVector[B] = { val buf = new VectorBuilder[B] @tailrec def go(v: NonEmptyVector[Either[A, B]]): Unit = v.head match { diff --git a/core/src/main/scala/cats/data/OneAnd.scala b/core/src/main/scala/cats/data/OneAnd.scala index bd40b7c99b..72e5f8314b 100644 --- a/core/src/main/scala/cats/data/OneAnd.scala +++ b/core/src/main/scala/cats/data/OneAnd.scala @@ -117,6 +117,9 @@ private[data] sealed trait OneAndInstances extends OneAndLowPriority2 { new NonEmptyReducible[OneAnd[F, ?], F] { override def split[A](fa: OneAnd[F, A]): (A, F[A]) = (fa.head, fa.tail) + override def get[A](fa: OneAnd[F, A])(idx: Long): Option[A] = + if (idx == 0L) Some(fa.head) else F.get(fa.tail)(idx - 1L) + override def size[A](fa: OneAnd[F, A]): Long = 1 + F.size(fa.tail) } diff --git a/core/src/main/scala/cats/data/Validated.scala b/core/src/main/scala/cats/data/Validated.scala index 725699d47a..caeaf83a40 100644 --- a/core/src/main/scala/cats/data/Validated.scala +++ b/core/src/main/scala/cats/data/Validated.scala @@ -404,6 +404,9 @@ private[data] sealed abstract class ValidatedInstances2 { override def size[A](fa: Validated[E, A]): Long = fa.fold(_ => 0L, _ => 1L) + override def get[A](fa: Validated[E, A])(idx: Long): Option[A] = + if (idx == 0L) fa.toOption else None + override def foldMap[A, B](fa: Validated[E, A])(f: A => B)(implicit B: Monoid[B]): B = fa.fold(_ => B.empty, f) diff --git a/core/src/main/scala/cats/instances/either.scala b/core/src/main/scala/cats/instances/either.scala index d7c8278996..1a02d752b9 100644 --- a/core/src/main/scala/cats/instances/either.scala +++ b/core/src/main/scala/cats/instances/either.scala @@ -112,6 +112,9 @@ trait EitherInstances extends cats.kernel.instances.EitherInstances { override def size[B](fab: Either[A, B]): Long = fab.fold(_ => 0L, _ => 1L) + override def get[B](fab: Either[A, B])(idx: Long): Option[B] = + if (idx == 0L) fab.fold(_ => None, Some(_)) else None + override def foldMap[B, C](fab: Either[A, B])(f: B => C)(implicit C: Monoid[C]): C = fab.fold(_ => C.empty, f) diff --git a/core/src/main/scala/cats/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index 5115268bdb..860ad5c3dc 100644 --- a/core/src/main/scala/cats/instances/list.scala +++ b/core/src/main/scala/cats/instances/list.scala @@ -70,6 +70,16 @@ trait ListInstances extends cats.kernel.instances.ListInstances { G.map2Eval(f(a), lglb)(_ :: _) }.value + @tailrec + override def get[A](fa: List[A])(idx: Long): Option[A] = + fa match { + case Nil => None + case h :: tail => + if (idx < 0) None + else if (idx == 0) Some(h) + else get(tail)(idx - 1) + } + override def exists[A](fa: List[A])(p: A => Boolean): Boolean = fa.exists(p) diff --git a/core/src/main/scala/cats/instances/map.scala b/core/src/main/scala/cats/instances/map.scala index a9133815d8..3d0df464e2 100644 --- a/core/src/main/scala/cats/instances/map.scala +++ b/core/src/main/scala/cats/instances/map.scala @@ -77,6 +77,12 @@ trait MapInstances extends cats.kernel.instances.MapInstances { override def size[A](fa: Map[K, A]): Long = fa.size.toLong + override def get[A](fa: Map[K, A])(idx: Long): Option[A] = { + if (idx >= 0L && idx < fa.size && idx < Int.MaxValue) + Some(fa.valuesIterator.drop(idx.toInt - 1).next) + else None + } + override def isEmpty[A](fa: Map[K, A]): Boolean = fa.isEmpty override def foldM[G[_], A, B](fa: Map[K, A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] = diff --git a/core/src/main/scala/cats/instances/option.scala b/core/src/main/scala/cats/instances/option.scala index 6799152fa9..49c3cf3690 100644 --- a/core/src/main/scala/cats/instances/option.scala +++ b/core/src/main/scala/cats/instances/option.scala @@ -86,6 +86,9 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances { override def maximumOption[A](fa: Option[A])(implicit A: Order[A]): Option[A] = fa + override def get[A](fa: Option[A])(idx: Long): Option[A] = + if (idx == 0L) fa else None + 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 = diff --git a/core/src/main/scala/cats/instances/set.scala b/core/src/main/scala/cats/instances/set.scala index 4c2e75421d..171e8ee91f 100644 --- a/core/src/main/scala/cats/instances/set.scala +++ b/core/src/main/scala/cats/instances/set.scala @@ -1,6 +1,8 @@ package cats package instances +import scala.annotation.tailrec + import cats.syntax.show._ trait SetInstances extends cats.kernel.instances.SetInstances { @@ -18,6 +20,19 @@ trait SetInstances extends cats.kernel.instances.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 get[A](fa: Set[A])(idx: Long): Option[A] = { + @tailrec + def go(idx: Int, it: Iterator[A]): Option[A] = { + if (it.hasNext) { + if (idx == 0) Some(it.next) else { + it.next + go(idx - 1, it) + } + } else None + } + if (idx < Int.MaxValue && idx >= 0L) go(idx.toInt, fa.toIterator) else None + } + override def size[A](fa: Set[A]): Long = fa.size.toLong override def exists[A](fa: Set[A])(p: A => Boolean): Boolean = diff --git a/core/src/main/scala/cats/instances/stream.scala b/core/src/main/scala/cats/instances/stream.scala index 19a7c71194..9730afe623 100644 --- a/core/src/main/scala/cats/instances/stream.scala +++ b/core/src/main/scala/cats/instances/stream.scala @@ -101,6 +101,17 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances { override def forall[A](fa: Stream[A])(p: A => Boolean): Boolean = fa.forall(p) + override def get[A](fa: Stream[A])(idx: Long): Option[A] = { + @tailrec + def go(idx: Long, s: Stream[A]): Option[A] = + s match { + case h #:: tail => + if (idx == 0L) Some(h) else go(idx - 1L, tail) + case _ => None + } + if (idx < 0L) None else go(idx, fa) + } + override def isEmpty[A](fa: Stream[A]): Boolean = fa.isEmpty override def filter[A](fa: Stream[A])(f: A => Boolean): Stream[A] = fa.filter(f) diff --git a/core/src/main/scala/cats/instances/try.scala b/core/src/main/scala/cats/instances/try.scala index 26c5f0e2ad..31f8698552 100644 --- a/core/src/main/scala/cats/instances/try.scala +++ b/core/src/main/scala/cats/instances/try.scala @@ -91,6 +91,9 @@ trait TryInstances extends TryInstances1 { override def reduceRightOption[A](fa: Try[A])(f: (A, Eval[A]) => Eval[A]): Eval[Option[A]] = Now(fa.toOption) + override def get[A](fa: Try[A])(idx: Long): Option[A] = + if (idx == 0L) fa.toOption else None + override def size[A](fa: Try[A]): Long = fa match { case Failure(_) => 0L diff --git a/core/src/main/scala/cats/instances/tuple.scala b/core/src/main/scala/cats/instances/tuple.scala index b2987994ac..ffa103c001 100644 --- a/core/src/main/scala/cats/instances/tuple.scala +++ b/core/src/main/scala/cats/instances/tuple.scala @@ -66,6 +66,9 @@ sealed trait Tuple2Instances extends Tuple2Instances1 { override def size[A](fa: (X, A)): Long = 1L + override def get[A](fa: (X, A))(idx: Long): Option[A] = + if (idx == 0L) Some(fa._2) else None + 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) diff --git a/core/src/main/scala/cats/instances/vector.scala b/core/src/main/scala/cats/instances/vector.scala index 827c2f86d8..382a64ff38 100644 --- a/core/src/main/scala/cats/instances/vector.scala +++ b/core/src/main/scala/cats/instances/vector.scala @@ -73,6 +73,9 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances { override def size[A](fa: Vector[A]): Long = fa.size.toLong + override def get[A](fa: Vector[A])(idx: Long): Option[A] = + if (idx < Int.MaxValue && fa.size > idx && idx >= 0) Some(fa(idx.toInt)) else None + 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)(_ +: _) diff --git a/core/src/main/scala/cats/package.scala b/core/src/main/scala/cats/package.scala index cf7b9066d6..3f7cdd0bb1 100644 --- a/core/src/main/scala/cats/package.scala +++ b/core/src/main/scala/cats/package.scala @@ -70,6 +70,8 @@ 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 get[A](fa: Id[A])(idx: Long): Option[A] = + if (idx == 0L) Some(fa) else None override def isEmpty[A](fa: Id[A]): Boolean = false } diff --git a/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index e64254eb6d..9b4cb71059 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -6,16 +6,22 @@ import org.scalacheck.Arbitrary import scala.util.Try import cats.instances.all._ -import cats.data.{NonEmptyList, NonEmptyStream, NonEmptyVector, Validated} +import cats.data._ 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(s"Foldable[$name].size") { - forAll { (fa: F[Int]) => - fa.size should === (iterator(fa).size.toLong) + test(s"Foldable[$name].size/get") { + forAll { (fa: F[Int], n: Int) => + val s = fa.size + s should === (iterator(fa).size.toLong) + if (n < s && n >= 0) { + fa.get(n.toLong) === Some(iterator(fa).take(n + 1).toList.last) + } else { + fa.get(n.toLong) === None + } } } @@ -217,7 +223,7 @@ 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) + def iterator[T](map: Map[Int, T]): Iterator[T] = map.valuesIterator } class FoldableOptionCheck extends FoldableCheck[Option]("option") { @@ -235,3 +241,38 @@ class FoldableValidatedCheck extends FoldableCheck[Validated[String, ?]]("valida class FoldableTryCheck extends FoldableCheck[Try]("try") { def iterator[T](tryt: Try[T]): Iterator[T] = tryt.toOption.iterator } + +class FoldableEitherKCheck extends FoldableCheck[EitherK[Option, Option, ?]]("eitherK") { + def iterator[T](eitherK: EitherK[Option, Option, T]) = eitherK.run.bimap(_.iterator, _.iterator).merge +} + +class FoldableIorCheck extends FoldableCheck[Int Ior ?]("ior") { + def iterator[T](ior: Int Ior T) = + ior.fold(_ => None.iterator, b => Some(b).iterator, (_, b) => Some(b).iterator) +} + +class FoldableIdCheck extends FoldableCheck[Id[?]]("id") { + def iterator[T](id: Id[T]) = Some(id).iterator +} + +class FoldableIdTCheck extends FoldableCheck[IdT[Option, ?]]("idT") { + def iterator[T](idT: IdT[Option, T]) = idT.value.iterator +} + +class FoldableConstCheck extends FoldableCheck[Const[Int, ?]]("const") { + def iterator[T](const: Const[Int, T]) = None.iterator +} + +class FoldableTuple2Check extends FoldableCheck[(Int, ?)]("tuple2") { + def iterator[T](tuple: (Int, T)) = Some(tuple._2).iterator +} + +class FoldableOneAndCheck extends FoldableCheck[OneAnd[List, ?]]("oneAnd") { + def iterator[T](oneAnd: OneAnd[List, T]) = (oneAnd.head :: oneAnd.tail).iterator +} + +class FoldableComposedCheck extends FoldableCheck[Nested[List, Option, ?]]("nested") { + def iterator[T](nested: Nested[List, Option, T]) = nested.value.collect { + case Some(t) => t + }.iterator +}