Skip to content

Commit

Permalink
Add Foldable extension collectFirstSomeM
Browse files Browse the repository at this point in the history
  • Loading branch information
catostrophe committed Aug 11, 2018
1 parent 9a59eb6 commit 743f0f9
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 1 deletion.
27 changes: 27 additions & 0 deletions core/src/main/scala/cats/syntax/foldable.scala
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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
}
10 changes: 9 additions & 1 deletion tests/src/test/scala/cats/tests/FoldableSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 743f0f9

Please sign in to comment.