-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
splitWhen & splitWhenM for Foldable #4855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
39f9ec6
adec14b
44a9ea1
1012418
665d7e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -23,6 +23,7 @@ package cats | |||||
|
|
||||||
| import scala.collection.mutable | ||||||
| import cats.kernel.CommutativeMonoid | ||||||
| import cats.data.NonEmptyList | ||||||
|
|
||||||
| import Foldable.{sentinel, Source} | ||||||
|
|
||||||
|
|
@@ -960,6 +961,60 @@ trait Foldable[F[_]] extends UnorderedFoldable[F] with FoldableNFunctions[F] { s | |||||
| import cats.instances.either.* | ||||||
| partitionBifoldM[G, Either, A, B, C](fa)(f)(A, M, Bifoldable[Either]) | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Split this Foldable into a NonEmptyList of Lists based on a predicate. | ||||||
| * The behaviour is aimed to be identical to that of haskell's `splitWhen` | ||||||
| * | ||||||
| * {{{ | ||||||
| * scala> import cats.syntax.all._, cats.Foldable, cats.data.NonEmptyList | ||||||
| * scala> Foldable[List].splitWhen(List(1,1))(_ == 1) | ||||||
| * res0: NonEmptyList[List[Int]] = NonEmptyList(List(), List(), List()) | ||||||
| * scala> Foldable[List].splitWhen(Nil)(_ == 1) | ||||||
| * res1: NonEmptyList[List[Nothing]] = NonEmptyList(List()) | ||||||
| * scala> Foldable[List].splitWhen(List(1, 2, 3, 1, 4, 5))(_ == 1) | ||||||
| * res2: NonEmptyList[List[Int]] = NonEmptyList(List(), List(2, 3), List(4, 5)) | ||||||
| * }}} | ||||||
| */ | ||||||
|
|
||||||
| def splitWhen[A](fa: F[A])(f: A => Boolean)(implicit | ||||||
| FA: Alternative[F] | ||||||
| ): NonEmptyList[F[A]] = { | ||||||
| foldRight(fa, Eval.now(NonEmptyList.one(FA.empty[A]))) { | ||||||
| case (a, acc) if f(a) => acc.map(FA.empty[A] :: _) | ||||||
| case (a, acc) => acc.map(nel => NonEmptyList(FA.prependK(a, nel.head), nel.tail)) | ||||||
| }.value | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Split this Foldable into a NonEmptyList of Lists based on the effectufl predicate. Monadic version of `splitWhen` | ||||||
| * | ||||||
| * {{{ | ||||||
| * scala> import cats.syntax.all._, cats.Foldable, cats.Eval, cats.data.NonEmptyList | ||||||
| * scala> Foldable[List].splitWhenM(List(1,1))(x => Eval.now(x == 1)).value | ||||||
| * res0: NonEmptyList[List[Int]] = NonEmptyList(List(), List(), List()) | ||||||
| * scala> Foldable[List].splitWhenM(List.empty[Int])(x => Eval.now(x == 1)).value | ||||||
| * res1: NonEmptyList[List[Int]] = NonEmptyList(List()) | ||||||
| * scala> Foldable[List].splitWhenM(List(1, 2, 3, 1, 4, 5))(x => Eval.now(x == 1)).value | ||||||
| * val res2: NonEmptyList[List[Int]] = NonEmptyList(List(), List(2, 3), List(4, 5)) | ||||||
| * }}} | ||||||
| */ | ||||||
|
|
||||||
| def splitWhenM[G[_], A](fa: F[A])(f: A => G[Boolean])(implicit | ||||||
| M: Monad[G], | ||||||
| FA: Alternative[F] | ||||||
| ): G[NonEmptyList[F[A]]] = { | ||||||
| foldRight(fa, Eval.now(M.pure(NonEmptyList.one(FA.empty[A])))) { (a, evalGnel) => | ||||||
| evalGnel.map { gnel => | ||||||
| M.flatMap(f(a)) { isDelimiter => | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the loss of tailRecM here means this isn't stack safe and will likely cause pain for users when they try to use it with Reader/Writer/State/Kleisli/etc...
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if a truly stack-safe implementation is possible here inside Foldable yet I may be wrong. But if it turns out to be true then we'd probably have to either remove that method or bring it all back to list...
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be getting it wrong, but I couldn't come up with an example where we'd blow the stack here.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As @johnynek mentioned, it'd blow the stack if G is Kleisli, Writer or Reader. I tried running it with Kleisli on my machine and just 10K element list was enough to get a StackOverflowError |
||||||
| M.map(gnel) { nel => | ||||||
| if (isDelimiter) FA.empty[A] :: nel | ||||||
| else NonEmptyList(FA.prependK(a, nel.head), nel.tail) | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
| }.value | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| object Foldable { | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.