From 743f0f912fa590b583999e5723c38bfa3fc200f5 Mon Sep 17 00:00:00 2001 From: catostrophe <40268503+catostrophe@users.noreply.github.com> Date: Sat, 11 Aug 2018 20:32:36 +0300 Subject: [PATCH 1/4] Add Foldable extension collectFirstSomeM --- .../src/main/scala/cats/syntax/foldable.scala | 27 +++++++++++++++++++ .../test/scala/cats/tests/FoldableSuite.scala | 10 ++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index f32d8bb5c4..3a7f21646b 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -1,6 +1,8 @@ package cats package syntax +import cats.data.OptionT + trait FoldableSyntax extends Foldable.ToFoldableOps with UnorderedFoldable.ToUnorderedFoldableOps { implicit final def catsSyntaxNestedFoldable[F[_]: Foldable, G[_], A](fga: F[G[A]]): NestedFoldableOps[F, G, A] = new NestedFoldableOps[F, G, A](fga) @@ -92,4 +94,29 @@ final class FoldableOps[F[_], A](val fa: F[A]) extends AnyVal { b.toString.dropRight(delim.length) } + suffix } + + /** + * Monadic version of `collectFirstSome`. + * {{{ + * scala> import cats.implicits._ + * scala> def parseInt(s: String): Either[String, Int] = Either.catchOnly[NumberFormatException](s.toInt).leftMap(_.getMessage) + * scala> val keys1 = List("1", "2", "4", "5") + * scala> val map1 = Map(4 -> "Four", 5 -> "Five") + * scala> keys1.collectFirstSomeM(parseInt(_) map map1.get) + * res1: scala.util.Either[String,Option[String]] = Right(Some(Four)) + * scala> val map2 = Map(6 -> "Six", 7 -> "Seven") + * scala> keys1.collectFirstSomeM(parseInt(_) map map2.get) + * res2: scala.util.Either[String,Option[String]] = Right(None) + * scala> val keys2 = List("1", "x", "4", "5") + * scala> keys2.collectFirstSomeM(parseInt(_) map map1.get) + * res3: scala.util.Either[String,Option[String]] = Left(For input string: "x") + * scala> val keys3 = List("1", "2", "4", "x") + * scala> keys3.collectFirstSomeM(parseInt(_) map map1.get) + * res4: scala.util.Either[String,Option[String]] = Right(Some(Four)) + * }}} + */ + def collectFirstSomeM[G[_], B](f: A => G[Option[B]])(implicit F: Foldable[F], G: Monad[G]): G[Option[B]] = + F.foldRight(fa, Eval.now(OptionT.none[G, B]))((a, lb) => + Eval.now(OptionT(f(a)).orElse(lb.value)) + ).value.value } diff --git a/tests/src/test/scala/cats/tests/FoldableSuite.scala b/tests/src/test/scala/cats/tests/FoldableSuite.scala index 09fab7b113..800929f2da 100644 --- a/tests/src/test/scala/cats/tests/FoldableSuite.scala +++ b/tests/src/test/scala/cats/tests/FoldableSuite.scala @@ -140,12 +140,20 @@ abstract class FoldableSuite[F[_]: Foldable](name: String)( fa.toList should === (iterator(fa).toList) } } - + test(s"Foldable[$name] mkString_") { forAll { (fa: F[Int]) => fa.mkString_("L[", ";", "]") should === (fa.toList.mkString("L[", ";", "]")) } } + + test(s"Foldable[$name].collectFirstSomeM") { + forAll { (fa: F[Int], n: Int) => + fa.collectFirstSomeM(x => (x > n).guard[Option].as(x).asRight[String]) should === (fa.toList.collectFirst { + case x if x > n => x + }.asRight[String]) + } + } } class FoldableSuiteAdditional extends CatsSuite { From b45aa148e983c98665e4d7b3b3ec9639a6f7bcfa Mon Sep 17 00:00:00 2001 From: catostrophe <40268503+catostrophe@users.noreply.github.com> Date: Sat, 11 Aug 2018 21:44:50 +0300 Subject: [PATCH 2/4] Implement without OptionT --- core/src/main/scala/cats/syntax/foldable.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 3a7f21646b..9f576e57b8 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -1,8 +1,6 @@ package cats package syntax -import cats.data.OptionT - trait FoldableSyntax extends Foldable.ToFoldableOps with UnorderedFoldable.ToUnorderedFoldableOps { implicit final def catsSyntaxNestedFoldable[F[_]: Foldable, G[_], A](fga: F[G[A]]): NestedFoldableOps[F, G, A] = new NestedFoldableOps[F, G, A](fga) @@ -116,7 +114,10 @@ final class FoldableOps[F[_], A](val fa: F[A]) extends AnyVal { * }}} */ def collectFirstSomeM[G[_], B](f: A => G[Option[B]])(implicit F: Foldable[F], G: Monad[G]): G[Option[B]] = - F.foldRight(fa, Eval.now(OptionT.none[G, B]))((a, lb) => - Eval.now(OptionT(f(a)).orElse(lb.value)) - ).value.value + F.foldRight(fa, Eval.now(G.pure(Option.empty[B])))((a, lb) => + Eval.now(G.flatMap(f(a)) { + case s @ Some(_) => G.pure(s) + case None => lb.value + }) + ).value } From 6af9ee427d723cea860953233b2770965659ade8 Mon Sep 17 00:00:00 2001 From: catostrophe <40268503+catostrophe@users.noreply.github.com> Date: Sun, 12 Aug 2018 00:21:01 +0300 Subject: [PATCH 3/4] Optimize pattern matching --- core/src/main/scala/cats/syntax/foldable.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/scala/cats/syntax/foldable.scala b/core/src/main/scala/cats/syntax/foldable.scala index 9f576e57b8..86c50c62ce 100644 --- a/core/src/main/scala/cats/syntax/foldable.scala +++ b/core/src/main/scala/cats/syntax/foldable.scala @@ -116,8 +116,8 @@ final class FoldableOps[F[_], A](val fa: F[A]) extends AnyVal { def collectFirstSomeM[G[_], B](f: A => G[Option[B]])(implicit F: Foldable[F], G: Monad[G]): G[Option[B]] = F.foldRight(fa, Eval.now(G.pure(Option.empty[B])))((a, lb) => Eval.now(G.flatMap(f(a)) { - case s @ Some(_) => G.pure(s) case None => lb.value + case s => G.pure(s) }) ).value } From 8b8024afe733e73182d4b4a5abaa7dcf7f291ed4 Mon Sep 17 00:00:00 2001 From: catostrophe <40268503+catostrophe@users.noreply.github.com> Date: Sun, 12 Aug 2018 01:06:41 +0300 Subject: [PATCH 4/4] Empty commit