Skip to content
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
2 changes: 1 addition & 1 deletion core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import scala.annotation.implicitNotFound
* See: [[http://www.cs.nott.ac.uk/~pszgmh/fold.pdf A tutorial on the universality and expressiveness of fold]]
*/
@implicitNotFound("Could not find an instance of Foldable for ${F}")
@typeclass trait Foldable[F[_]] extends UnorderedFoldable[F] { self =>
@typeclass trait Foldable[F[_]] extends UnorderedFoldable[F] with FoldableNFunctions[F] { self =>

/**
* Left associative fold on 'F' using the function 'f'.
Expand Down
26 changes: 26 additions & 0 deletions core/src/main/scala/cats/syntax/foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,32 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal {
f: A => G[Either[B, C]]
)(implicit A: Alternative[F], F: Foldable[F], M: Monad[G]): G[(F[B], F[C])] =
F.partitionEitherM[G, A, B, C](fa)(f)(A, M)

def sliding2(implicit F: Foldable[F]): List[(A, A)] = F.sliding2(fa)
def sliding3(implicit F: Foldable[F]): List[(A, A, A)] = F.sliding3(fa)
def sliding4(implicit F: Foldable[F]): List[(A, A, A, A)] = F.sliding4(fa)
def sliding5(implicit F: Foldable[F]): List[(A, A, A, A, A)] = F.sliding5(fa)
def sliding6(implicit F: Foldable[F]): List[(A, A, A, A, A, A)] = F.sliding6(fa)
def sliding7(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A)] = F.sliding7(fa)
def sliding8(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A)] = F.sliding8(fa)
def sliding9(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A)] = F.sliding9(fa)
def sliding10(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A)] = F.sliding10(fa)
def sliding11(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A)] = F.sliding11(fa)
def sliding12(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A)] = F.sliding12(fa)
def sliding13(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A)] = F.sliding13(fa)
def sliding14(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A)] = F.sliding14(fa)
def sliding15(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] = F.sliding15(fa)
def sliding16(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] = F.sliding16(fa)
def sliding17(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] = F.sliding17(fa)
def sliding18(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] = F.sliding18(fa)
def sliding19(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] =
F.sliding19(fa)
def sliding20(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] =
F.sliding20(fa)
def sliding21(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] =
F.sliding21(fa)
def sliding22(implicit F: Foldable[F]): List[(A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A)] =
F.sliding22(fa)
}

@deprecated("Use methods on Foldable", "2.1.0-RC1")
Expand Down
58 changes: 57 additions & 1 deletion project/Boilerplate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ object Boilerplate {
GenTupleSemigroupalSyntax,
GenParallelArityFunctions,
GenParallelArityFunctions2,
GenTupleParallelSyntax
GenTupleParallelSyntax,
GenFoldableArityFunctions
)

val header = "// auto-generated boilerplate by /project/Boilerplate.scala" // TODO: put something meaningful here?
Expand Down Expand Up @@ -503,4 +504,59 @@ object Boilerplate {
}
}

object GenFoldableArityFunctions extends Template {
def filename(root: File) = root / "cats" / "FoldableNFunctions.scala"
override def range = 2 to maxArity
def content(tv: TemplateVals) = {
import tv._

val tupleTpe = (1 to arity).map(_ => "A").mkString("(", ", ", ")")
def listXN(range: Range) = range.map("x" + _).mkString(" :: ")
val reverseXN = listXN(1 to arity - 1)
val tupleXN = (1 to arity).map("x" + _).mkString("(", ", ", ")")

block"""
|package cats
|
|/**
| * @groupprio Ungrouped 0
| *
| * @groupname FoldableSlidingN foldable arity
| * @groupdesc FoldableSlidingN
| * Group sequential elements into fixed sized tuples by passing a "sliding window" over them.
| *
| * A foldable with fewer elements than the window size will return an empty list unlike `Iterable#sliding(size: Int)`.
| * Example:
| * {{{
| * import cats.Foldable
| * scala> Foldable[List].sliding2((1 to 10).toList)
| * val res0: List[(Int, Int)] = List((1,2), (2,3), (3,4), (4,5), (5,6), (6,7), (7,8), (8,9), (9,10))
| *
| * scala> Foldable[List].sliding4((1 to 10).toList)
| * val res1: List[(Int, Int, Int, Int)] = List((1,2,3,4), (2,3,4,5), (3,4,5,6), (4,5,6,7), (5,6,7,8), (6,7,8,9), (7,8,9,10))
| *
| * scala> Foldable[List].sliding4((1 to 2).toList)
| * val res2: List[(Int, Int, Int, Int)] = List()
| *
| * }}}
| *
| * @groupprio FoldableSlidingN 999
| *
| */
|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 to arity - 1)} :: Nil))
- case l =>
- Now((acc, x1 :: l))
- }
- }.value._1
|}
"""
}
}
}
82 changes: 82 additions & 0 deletions tests/src/test/scala/cats/tests/FoldableSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,88 @@ abstract class FoldableSuite[F[_]: Foldable](name: String)(implicit
)
}
}

test(s"Foldable[$name].sliding2 consistent with List#sliding(2)") {
forAll { (fi: F[Int]) =>
val n = 2
checkSlidingNConsistent(fi, n, Foldable[F].sliding2) { case x1 :: x2 :: Nil =>
(x1, x2)
}
}
}
test(s"Foldable[$name].sliding3 consistent with List#sliding(3)") {
forAll { (fi: F[Int]) =>
val n = 3
checkSlidingNConsistent(fi, n, Foldable[F].sliding3) { case x1 :: x2 :: x3 :: Nil =>
(x1, x2, x3)
}
}
}
test(s"Foldable[$name].sliding4 consistent with List#sliding(4)") {
forAll { (fi: F[Int]) =>
val n = 4
checkSlidingNConsistent(fi, n, Foldable[F].sliding4) { case x1 :: x2 :: x3 :: x4 :: Nil =>
(x1, x2, x3, x4)
}
}
}
test(s"Foldable[$name].sliding5 consistent with List#sliding(5)") {
forAll { (fi: F[Int]) =>
val n = 5
checkSlidingNConsistent(fi, n, Foldable[F].sliding5) { case x1 :: x2 :: x3 :: x4 :: x5 :: Nil =>
(x1, x2, x3, x4, x5)
}
}
}
test(s"Foldable[$name].sliding6 consistent with List#sliding(6)") {
forAll { (fi: F[Int]) =>
val n = 6
checkSlidingNConsistent(fi, n, Foldable[F].sliding6) { case x1 :: x2 :: x3 :: x4 :: x5 :: x6 :: Nil =>
(x1, x2, x3, x4, x5, x6)
}
}
}
test(s"Foldable[$name].sliding7 consistent with List#sliding(7)") {
forAll { (fi: F[Int]) =>
val n = 7
checkSlidingNConsistent(fi, n, Foldable[F].sliding7) { case x1 :: x2 :: x3 :: x4 :: x5 :: x6 :: x7 :: Nil =>
(x1, x2, x3, x4, x5, x6, x7)
}
}
}
test(s"Foldable[$name].sliding8 consistent with List#sliding(8)") {
forAll { (fi: F[Int]) =>
val n = 8
checkSlidingNConsistent(fi, n, Foldable[F].sliding8) { case x1 :: x2 :: x3 :: x4 :: x5 :: x6 :: x7 :: x8 :: Nil =>
(x1, x2, x3, x4, x5, x6, x7, x8)
}
}
}
// skip sliding 10-22 as arbitrary collections of that size aren't generated
test(s"Foldable[$name].sliding9 consistent with List#sliding(9)") {
forAll { (fi: F[Int]) =>
val n = 9
checkSlidingNConsistent(fi, n, Foldable[F].sliding9) {
case x1 :: x2 :: x3 :: x4 :: x5 :: x6 :: x7 :: x8 :: x9 :: Nil => (x1, x2, x3, x4, x5, x6, x7, x8, x9)
}
}
}

def checkSlidingNConsistent[Tup <: Product: Eq](fi: F[Int], n: Int, slidingN: F[Int] => List[Tup])(
pf: PartialFunction[List[Int], Tup]
): Unit = {
val result = slidingN(fi)
if (n <= fi.size) {
val expected = fi.toList
.sliding(n)
.map(pf)
.toList
assert(result === expected)
} else {
assert(result.isEmpty)
}
}

}

class FoldableSuiteAdditional extends CatsSuite with ScalaVersionSpecificFoldableSuite {
Expand Down