diff --git a/core/src/main/scala/cats/Foldable.scala b/core/src/main/scala/cats/Foldable.scala index 641ef1379dc..a75d8ec5163 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 < 0) None + else + foldM[Either[A, ?], A, Int](fa, 0) { (i, a) => + if (i == idx) Left(a) else Right(i + 1) + } 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/data/NonEmptyList.scala b/core/src/main/scala/cats/data/NonEmptyList.scala index a3c3b138175..d5c7bd17b4d 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 73d7e24ea94..78bee6f5523 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/instances/list.scala b/core/src/main/scala/cats/instances/list.scala index 5115268bdb7..9a69d948a0a 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/vector.scala b/core/src/main/scala/cats/instances/vector.scala index 827c2f86d8e..382a64ff381 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/tests/src/test/scala/cats/tests/FoldableTests.scala b/tests/src/test/scala/cats/tests/FoldableTests.scala index e64254eb6dc..c474af3ff38 100644 --- a/tests/src/test/scala/cats/tests/FoldableTests.scala +++ b/tests/src/test/scala/cats/tests/FoldableTests.scala @@ -13,9 +13,15 @@ abstract class FoldableCheck[F[_]: Foldable](name: String)(implicit ArbFInt: Arb 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 + } } }