Skip to content

Commit

Permalink
Merge pull request #4067 from joroKr21/sliding-n
Browse files Browse the repository at this point in the history
More efficient slidingN functions
  • Loading branch information
johnynek authored Dec 4, 2021
2 parents e52dd7e + 86d555f commit abd6c77
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 48 deletions.
15 changes: 3 additions & 12 deletions project/Boilerplate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -549,9 +549,8 @@ object Boilerplate {
def content(tv: TemplateVals) = {
import tv._

val tupleTpe = (1 to arity).map(_ => "A").mkString("(", ", ", ")")
def listXN(range: Range) = range.map("x" + _).mkString(" :: ")
val tupleXN = (1 to arity).map("x" + _).mkString("(", ", ", ")")
val tupleTpe = Iterator.fill(arity)("A").mkString("(", ", ", ")")
val tupleXN = Iterator.tabulate(arity)(i => s"x($i)").mkString("(", ", ", ")")

block"""
|package cats
Expand Down Expand Up @@ -584,15 +583,7 @@ object Boilerplate {
|trait FoldableNFunctions[F[_]] { self: Foldable[F] =>
- /** @group FoldableSlidingN */
- def sliding$arity[A](fa: F[A]): List[$tupleTpe] =
- foldRight(fa, Now((List.empty[$tupleTpe], List.empty[A]))) { (x1, eval) =>
- val (acc, l) = eval.value
- l match {
- case ${listXN(2 to arity)} :: Nil =>
- Now(($tupleXN :: acc, ${listXN(1 until arity)} :: Nil))
- case l =>
- Now((acc, x1 :: l))
- }
- }.value._1
- toIterable(fa).iterator.sliding($arity).withPartial(false).map(x => $tupleXN).toList
|}
"""
}
Expand Down
15 changes: 9 additions & 6 deletions tests/src/test/scala-2.13+/cats/tests/ScalaVersionSpecific.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@ import cats.laws.discipline.eq._
import org.scalacheck.Arbitrary

trait ScalaVersionSpecificFoldableSuite { self: FoldableSuiteAdditional =>
test("Foldable[LazyList].foldM stack safety") {
checkMonadicFoldsStackSafety[LazyList](_.to(LazyList))
}
test("Foldable[LazyList] monadic folds stack safety")(checkMonadicFoldsStackSafety(_.to(LazyList)))
test("Foldable[LazyList].slidingN stack safety")(checkSlidingNStackSafety(_.to(LazyList)))

test("Foldable[NonEmptyLazyList].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[NonEmptyLazyList](xs => NonEmptyLazyList(xs.head, xs.tail: _*))
}
test("Foldable[NonEmptyLazyList] monadic folds stack safety")(
checkMonadicFoldsStackSafety(xs => NonEmptyLazyList(xs.head, xs.tail: _*))
)

test("Foldable[NonEmptyLazyList].slidingN stack safety")(
checkSlidingNStackSafety(xs => NonEmptyLazyList(xs.head, xs.tail: _*))
)

private def bombLazyList[A]: A = sys.error("boom")
private val dangerousLazyList = 0 #:: 1 #:: 2 #:: bombLazyList[Int] #:: LazyList.empty
Expand Down
74 changes: 44 additions & 30 deletions tests/src/test/scala/cats/tests/FoldableSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldabl
val lazySum: Eval[Int] = F.foldRightDefer(large, boom[Int])((elem, acc) => acc.map(_ + elem))
}

def checkMonadicFoldsStackSafety[F[_]](fromRange: Range => F[Int])(implicit F: Foldable[F]): Unit = {
def checkMonadicFoldsStackSafety[F[_]: Foldable](fromRange: Range => F[Int]): Unit = {
def nonzero(acc: Long, x: Int): Option[Long] =
if (x == 0) None else Some(acc + x)

Expand All @@ -479,15 +479,15 @@ class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldabl
val src = fromRange(1 to n)

val foldMExpected = n.toLong * (n.toLong + 1) / 2
val foldMResult = F.foldM(src, 0L)(nonzero)
val foldMResult = src.foldM(0L)(nonzero)
assert(foldMResult.get == foldMExpected)

val existsMExpected = true
val existsMResult = F.existsM(src)(gte(n, _))
val existsMResult = src.existsM(gte(n, _))
assert(existsMResult.get == existsMExpected)

val forallMExpected = true
val forallMResult = F.forallM(src)(gte(0, _))
val forallMResult = src.forallM(gte(0, _))
assert(forallMResult.get == forallMExpected)

val findMExpected = Some(n)
Expand All @@ -497,8 +497,15 @@ class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldabl
val collectFirstSomeMExpected = Some(n)
val collectFirstSomeMResult = src.collectFirstSomeM(gteSome(n, _))
assert(collectFirstSomeMResult.get == collectFirstSomeMExpected)
}

def checkSlidingNStackSafety[F[_]: Foldable](fromRange: Range => F[Int]): Unit = {
val n = 1000
val src = fromRange(1 to n)

()
val sliding2Expected = List.tabulate(n)(i => (i, i + 1)).tail
val sliding2Result = src.sliding2
assertEquals(sliding2Result, sliding2Expected)
}

test(s"Foldable.iterateRight") {
Expand All @@ -514,39 +521,46 @@ class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldabl
}
}

test("Foldable[List].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[List](_.toList)
}
test("Foldable[List] monadic folds stack safety")(checkMonadicFoldsStackSafety(_.toList))
test("Foldable[List].slidingN stack safety")(checkSlidingNStackSafety(_.toList))

test("Foldable[Stream].foldM stack safety") {
checkMonadicFoldsStackSafety[Stream](_.toStream)
}
test("Foldable[Stream] monadic folds stack safety")(checkMonadicFoldsStackSafety(_.toStream))
test("Foldable[Stream].slidingN stack safety")(checkSlidingNStackSafety(_.toStream))

test("Foldable[Vector].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[Vector](_.toVector)
}
test("Foldable[Vector] monadic folds stack safety")(checkMonadicFoldsStackSafety(_.toVector))
test("Foldable[Vector].slidingN stack safety")(checkSlidingNStackSafety(_.toVector))

test("Foldable[SortedSet].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[SortedSet](s => SortedSet(s: _*))
}
test("Foldable[SortedSet] monadic folds stack safety")(checkMonadicFoldsStackSafety(xs => SortedSet(xs: _*)))
test("Foldable[SortedSet].slidingN stack safety")(checkSlidingNStackSafety(xs => SortedSet(xs: _*)))

test("Foldable[SortedMap[String, *]].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[SortedMap[String, *]](xs =>
SortedMap.empty[String, Int] ++ xs.map(x => x.toString -> x).toMap
)
// Can't checkSlidingNStackSafety because of iteration order.
test("Foldable[SortedMap[String, *]] monadic stack safety") {
checkMonadicFoldsStackSafety(xs => SortedMap(xs.map(x => x.toString -> x): _*))
}

test("Foldable[NonEmptyList].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[NonEmptyList](xs => NonEmptyList.fromListUnsafe(xs.toList))
}
test("Foldable[NonEmptyList] monadic folds stack safety")(
checkMonadicFoldsStackSafety(xs => NonEmptyList.fromListUnsafe(xs.toList))
)

test("Foldable[NonEmptyVector].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[NonEmptyVector](xs => NonEmptyVector.fromVectorUnsafe(xs.toVector))
}
test("Foldable[NonEmptyList].slidingN stack safety")(
checkSlidingNStackSafety(xs => NonEmptyList.fromListUnsafe(xs.toList))
)

test("Foldable[NonEmptyStream].foldM/existsM/forallM/findM/collectFirstSomeM stack safety") {
checkMonadicFoldsStackSafety[NonEmptyStream](xs => NonEmptyStream(xs.head, xs.tail: _*))
}
test("Foldable[NonEmptyVector] monadic folds stack safety")(
checkMonadicFoldsStackSafety(xs => NonEmptyVector.fromVectorUnsafe(xs.toVector))
)

test("Foldable[NonEmptyVector].slidingN stack safety")(
checkSlidingNStackSafety(xs => NonEmptyVector.fromVectorUnsafe(xs.toVector))
)

test("Foldable[NonEmptyStream] monadic folds stack safety")(
checkMonadicFoldsStackSafety(xs => NonEmptyStream(xs.head, xs.tail: _*))
)

test("Foldable[NonEmptyStream].slidingN stack safety")(
checkSlidingNStackSafety(xs => NonEmptyStream(xs.head, xs.tail: _*))
)

val F = Foldable[Stream]
def bomb[A]: A = sys.error("boom")
Expand Down

0 comments on commit abd6c77

Please sign in to comment.