Skip to content

Commit 16a96d8

Browse files
move splitWhen back to Foldable
1 parent 1012418 commit 16a96d8

5 files changed

Lines changed: 94 additions & 85 deletions

File tree

core/src/main/scala/cats/Foldable.scala

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ package cats
2323

2424
import scala.collection.mutable
2525
import cats.kernel.CommutativeMonoid
26+
import cats.data.NonEmptyList
2627

2728
import Foldable.{sentinel, Source}
2829

@@ -960,6 +961,60 @@ trait Foldable[F[_]] extends UnorderedFoldable[F] with FoldableNFunctions[F] { s
960961
import cats.instances.either.*
961962
partitionBifoldM[G, Either, A, B, C](fa)(f)(A, M, Bifoldable[Either])
962963
}
964+
965+
/**
966+
* Split this Foldable into a NonEmptyList of Lists based on a predicate.
967+
* The behaviour is aimed to be identical to that of haskell's `splitWhen`
968+
*
969+
* {{{
970+
* scala> import cats.syntax.all._, cats.Foldable, cats.data.NonEmptyList
971+
* scala> Foldable[List].splitWhen(List(1,1))(_ == 1)
972+
* res0: NonEmptyList[List[Int]] = NonEmptyList(List(), List(), List())
973+
* scala> Foldable[List].splitWhen(Nil)(_ == 1)
974+
* res1: NonEmptyList[List[Nothing]] = NonEmptyList(List())
975+
* scala> Foldable[List].splitWhen(List(1, 2, 3, 1, 4, 5))(_ == 1)
976+
* res2: NonEmptyList[List[Int]] = NonEmptyList(List(), List(2, 3), List(4, 5))
977+
* }}}
978+
*/
979+
980+
def splitWhen[A](fa: F[A])(f: A => Boolean)(implicit
981+
FA: Alternative[F]
982+
): NonEmptyList[F[A]] = {
983+
foldRight(fa, Eval.now(NonEmptyList.one(FA.empty[A]))) {
984+
case (a, acc) if f(a) => acc.map(FA.empty[A] :: _)
985+
case (a, acc) => acc.map(nel => NonEmptyList(FA.prependK(a, nel.head), nel.tail))
986+
}.value
987+
}
988+
989+
/**
990+
* Split this Foldable into a NonEmptyList of Lists based on the effectufl predicate. Monadic version of `splitWhen`
991+
*
992+
* {{{
993+
* scala> import cats.syntax.all._, cats.Foldable, cats.Eval, cats.data.NonEmptyList
994+
* scala> Foldable[List].splitWhenM(List(1,1))(x => Eval.now(x == 1)).value
995+
* res0: NonEmptyList[List[Int]] = NonEmptyList(List(), List(), List())
996+
* scala> Foldable[List].splitWhenM(List.empty[Int])(x => Eval.now(x == 1)).value
997+
* res1: NonEmptyList[List[Int]] = NonEmptyList(List())
998+
* scala> Foldable[List].splitWhenM(List(1, 2, 3, 1, 4, 5))(x => Eval.now(x == 1)).value
999+
* val res2: NonEmptyList[List[Int]] = NonEmptyList(List(), List(2, 3), List(4, 5))
1000+
* }}}
1001+
*/
1002+
1003+
def splitWhenM[G[_], A](fa: F[A])(f: A => G[Boolean])(implicit
1004+
M: Monad[G],
1005+
FA: Alternative[F]
1006+
): G[NonEmptyList[F[A]]] = {
1007+
foldRight(fa, Eval.now(M.pure(NonEmptyList.one(FA.empty[A])))) { (a, evalGnel) =>
1008+
evalGnel.map { gnel =>
1009+
M.flatMap(f(a)) { isDelimiter =>
1010+
M.map(gnel) { nel =>
1011+
if (isDelimiter) FA.empty[A] :: nel
1012+
else NonEmptyList(FA.prependK(a, nel.head), nel.tail)
1013+
}
1014+
}
1015+
}
1016+
}.value
1017+
}
9631018
}
9641019

9651020
object Foldable {

core/src/main/scala/cats/syntax/foldable.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
package cats
2323
package syntax
2424

25+
import cats.data.NonEmptyList
26+
2527
trait FoldableSyntax extends Foldable.ToFoldableOps with UnorderedFoldable.ToUnorderedFoldableOps {
2628

2729
implicit final def catsSyntaxNestedFoldable[F[_]: Foldable, G[_], A](fga: F[G[A]]): NestedFoldableOps[F, G, A] =
@@ -304,6 +306,16 @@ final class FoldableOps0[F[_], A](private val fa: F[A]) extends AnyVal {
304306
)(implicit A: Alternative[F], F: Foldable[F], M: Monad[G]): G[(F[B], F[C])] =
305307
F.partitionEitherM[G, A, B, C](fa)(f)(A, M)
306308

309+
def splitWhen(f: A => Boolean)(implicit FF: Foldable[F], FA: Alternative[F]): NonEmptyList[F[A]] = {
310+
FF.splitWhen[A](fa)(f)(FA)
311+
}
312+
313+
def splitWhenM[G[_]](
314+
f: A => G[Boolean]
315+
)(implicit FF: Foldable[F], FA: Alternative[F], G: Monad[G]): G[NonEmptyList[F[A]]] = {
316+
FF.splitWhenM[G, A](fa)(f)(G, FA)
317+
}
318+
307319
def sliding2(implicit F: Foldable[F]): List[(A, A)] =
308320
F.sliding2(fa)
309321
def sliding3(implicit F: Foldable[F]): List[(A, A, A)] =

core/src/main/scala/cats/syntax/list.scala

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -140,66 +140,6 @@ final class ListOps[A](private val la: List[A]) extends AnyVal {
140140
def scanRightNel[B](b: B)(f: (A, B) => B): NonEmptyList[B] =
141141
NonEmptyList.fromListUnsafe(la.scanRight(b)(f))
142142

143-
/**
144-
* Split this List into a NonEmptyList of Lists based on a predicate.
145-
* The behaviour is aimed to be identical to that of haskell's `splitWhen`
146-
*
147-
* {{{
148-
* scala> import cats.syntax.all._
149-
* scala> List(1, 1).splitWhen(_ == 1)
150-
* val res0: cats.data.NonEmptyList[List[Int]] = NonEmptyList(List(), List(), List())
151-
*
152-
* scala> List.empty[Int].splitWhen(_ == 1)
153-
* val res1: cats.data.NonEmptyList[List[Int]] = NonEmptyList(List())
154-
*
155-
* scala> List(1, 2, 3, 1, 4, 5).splitWhen(_ == 1)
156-
* val res2: cats.data.NonEmptyList[List[Int]] = NonEmptyList(List(), List(2, 3), List(4, 5))
157-
* }}}
158-
*/
159-
160-
def splitWhen(f: A => Boolean): NonEmptyList[List[A]] = {
161-
la.reverse.foldLeft(NonEmptyList.one(List.empty[A])) { case (lst, e) =>
162-
if (f(e)) Nil :: lst else NonEmptyList(e :: lst.head, lst.tail)
163-
}
164-
}
165-
166-
/**
167-
* Split this List into a NonEmptyList of Lists based on the effectufl predicate. Monadic version of `splitWhen`
168-
*
169-
* {{{
170-
* scala> import cats.syntax.all._, cats.Eval
171-
* scala> List(1, 1).splitWhenM(x => Eval.now(x == 1)).value
172-
* val res0: cats.data.NonEmptyList[List[Int]] = NonEmptyList(List(), List(), List())
173-
*
174-
* scala> List.empty[Int].splitWhenM(x => Eval.now(x == 1)).value
175-
* val res1: cats.data.NonEmptyList[List[Int]] = NonEmptyList(List())
176-
*
177-
* scala> List(1, 2, 3, 1, 4, 5).splitWhenM(x => Eval.now(x == 1)).value
178-
* val res2: cats.data.NonEmptyList[List[Int]] = NonEmptyList(List(), List(2, 3), List(4, 5))
179-
* }}}
180-
*/
181-
182-
def splitWhenM[G[_]](f: A => G[Boolean])(implicit M: Monad[G]): G[NonEmptyList[List[A]]] = {
183-
type State = (List[A], NonEmptyList[List[A]])
184-
185-
def step(state: State): G[Either[State, NonEmptyList[List[A]]]] =
186-
state match {
187-
case (e :: rest, acc) =>
188-
M.map(f(e)) { shouldSplit =>
189-
val nextAcc =
190-
if (shouldSplit) Nil :: acc
191-
else NonEmptyList(e :: acc.head, acc.tail)
192-
193-
Left((rest, nextAcc))
194-
}
195-
196-
case (Nil, acc) =>
197-
M.pure(Right(acc))
198-
}
199-
200-
M.tailRecM((la.reverse, NonEmptyList.one(List.empty[A])))(step)
201-
}
202-
203143
}
204144

205145
private[syntax] trait ListSyntaxBinCompat0 {

tests/shared/src/test/scala/cats/tests/FoldableSuite.scala

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,33 @@ abstract class FoldableSuite[F[_]: Foldable](name: String)(implicit
333333
}
334334
}
335335

336+
test(s"Foldable[$name].splitWhen") {
337+
forAll { (fi: F[Int], x: Int) =>
338+
val pred = (y: Int) => x == y
339+
val li = fi.toList
340+
val res = li.splitWhen(pred)
341+
val expectedFiltered = li.filterNot(pred)
342+
val expectedSize = li.size - expectedFiltered.size + 1
343+
assert(res.size === expectedSize)
344+
assert(res.reduce === expectedFiltered)
345+
assert(Reducible[NonEmptyList].nonEmptyIntercalate(res, List(x)) == li)
346+
}
347+
}
348+
349+
test(s"Foldable[$name].splitWhenM") {
350+
forAll { (fi: F[Int], x: Int) =>
351+
val pred = (y: Int) => x == y
352+
val predM = (y: Int) => Eval.now(pred(y))
353+
val li = fi.toList
354+
val res = li.splitWhenM(predM).value
355+
val expectedFiltered = li.filterNot(pred)
356+
val expectedSize = li.size - expectedFiltered.size + 1
357+
assert(res.size === expectedSize)
358+
assert(res.reduce === expectedFiltered)
359+
assert(Reducible[NonEmptyList].nonEmptyIntercalate(res, List(x)) == li)
360+
}
361+
}
362+
336363
test(s"Foldable[$name].sliding2 consistent with List#sliding(2)") {
337364
forAll { (fi: F[Int]) =>
338365
val n = 2

tests/shared/src/test/scala/cats/tests/ListSuite.scala

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -137,31 +137,6 @@ class ListSuite extends CatsSuite {
137137

138138
assert(sumAll == lst.sum)
139139
}
140-
141-
test("splitWhen") {
142-
forAll { (li: List[Int], x: Int) =>
143-
val pred = (y: Int) => x == y
144-
val res = li.splitWhen(pred)
145-
val expectedFiltered = li.filterNot(pred)
146-
val expectedSize = li.size - expectedFiltered.size + 1
147-
assert(res.size === expectedSize)
148-
assert(res.toList.flatten === expectedFiltered)
149-
assert(Reducible[NonEmptyList].nonEmptyIntercalate(res, List(x)) == li)
150-
}
151-
}
152-
153-
test("splitWhenM") {
154-
forAll { (li: List[Int], x: Int) =>
155-
val pred = (y: Int) => x == y
156-
val predM = (y: Int) => Eval.now(pred(y))
157-
val res = li.splitWhenM(predM).value
158-
val expectedFiltered = li.filterNot(pred)
159-
val expectedSize = li.size - expectedFiltered.size + 1
160-
assert(res.size === expectedSize)
161-
assert(res.toList.flatten === expectedFiltered)
162-
assert(Reducible[NonEmptyList].nonEmptyIntercalate(res, List(x)) == li)
163-
}
164-
}
165140
}
166141

167142
final class ListInstancesSuite extends munit.FunSuite {

0 commit comments

Comments
 (0)