Skip to content

Commit

Permalink
added collectFirst and collectFirstSome to Foldable (#2030)
Browse files Browse the repository at this point in the history
* added a collectFirst that takes A => Option[B]

* added mima exception

* fix scalastyle

* update docs

* improve doc example

* address feedback

* fix mima

* use the performance trick from traverseOnce

* address feedback

* fix build

* minor style change

* move sentinel to companion
  • Loading branch information
kailuowang authored Nov 21, 2017
1 parent ef64ff8 commit 9325184
Show file tree
Hide file tree
Showing 13 changed files with 96 additions and 2 deletions.
7 changes: 7 additions & 0 deletions alleycats-core/src/main/scala/alleycats/std/map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,12 @@ trait MapInstances {
A.combineAll(fa.values)

override def toList[A](fa: Map[K, A]): List[A] = fa.values.toList

override def collectFirst[A, B](fa: Map[K, A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(new PartialFunction[(K, A), B] {
override def isDefinedAt(x: (K, A)) = pf.isDefinedAt(x._2)
override def apply(v1: (K, A)) = pf(v1._2)
})

override def collectFirstSome[A, B](fa: Map[K, A])(f: A => Option[B]): Option[B] = collectFirst(fa)(Function.unlift(f))
}
}
6 changes: 6 additions & 0 deletions alleycats-core/src/main/scala/alleycats/std/set.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ object SetInstances {
fa.reduceLeftOption(f)

override def find[A](fa: Set[A])(f: A => Boolean): Option[A] = fa.find(f)

override def collectFirst[A, B](fa: Set[A])(pf: PartialFunction[A, B]): Option[B] =
fa.collectFirst(pf)

override def collectFirstSome[A, B](fa: Set[A])(f: A => Option[B]): Option[B] =
fa.collectFirst(Function.unlift(f))
}
}

Expand Down
7 changes: 6 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,12 @@ def mimaSettings(moduleName: String) = Seq(
exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdParallelForZipStream"),
exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdNonEmptyParallelForZipList"),
exclude[ReversedMissingMethodProblem]("cats.instances.ParallelInstances.catsStdParallelForFailFastFuture"),
exclude[DirectMissingMethodProblem]("cats.data.EitherTInstances2.catsDataMonadErrorForEitherT")
exclude[DirectMissingMethodProblem]("cats.data.EitherTInstances2.catsDataMonadErrorForEitherT"),
exclude[DirectMissingMethodProblem]("cats.data.EitherTInstances2.catsDataMonadErrorForEitherT"),
exclude[ReversedMissingMethodProblem]("cats.Foldable.collectFirstSome"),
exclude[ReversedMissingMethodProblem]("cats.Foldable.collectFirst"),
exclude[ReversedMissingMethodProblem]("cats.Foldable#Ops.collectFirstSome"),
exclude[ReversedMissingMethodProblem]("cats.Foldable#Ops.collectFirst")
)
}
)
Expand Down
32 changes: 32 additions & 0 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import scala.collection.mutable
import cats.instances.either._
import cats.instances.long._
import simulacrum.typeclass
import Foldable.sentinel

/**
* Data structures that can be folded to a summary value.
Expand Down Expand Up @@ -214,6 +215,35 @@ import simulacrum.typeclass
case Right(_) => None
}

def collectFirst[A, B](fa: F[A])(pf: PartialFunction[A, B]): Option[B] =
foldRight(fa, Eval.now(Option.empty[B])) { (a, lb) =>
// trick from TravsersableOnce
val x = pf.applyOrElse(a, sentinel)
if (x.asInstanceOf[AnyRef] ne sentinel) Eval.now(Some(x.asInstanceOf[B]))
else lb
}.value


/**
* Like `collectFirst` from `scala.collection.Traversable` but takes `A => Option[B]`
* instead of `PartialFunction`s.
* {{{
* scala> import cats.implicits._
* scala> val keys = List(1, 2, 4, 5)
* scala> val map = Map(4 -> "Four", 5 -> "Five")
* scala> keys.collectFirstSome(map.get)
* res0: Option[String] = Some(Four)
* scala> val map2 = Map(6 -> "Six", 7 -> "Seven")
* scala> keys.collectFirstSome(map2.get)
* res1: Option[String] = None
* }}}
*/
def collectFirstSome[A, B](fa: F[A])(f: A => Option[B]): Option[B] =
foldRight(fa, Eval.now(Option.empty[B])) { (a, lb) =>
val ob = f(a)
if (ob.isDefined) Eval.now(ob) else lb
}.value

/**
* Fold implemented using the given Monoid[A] instance.
*/
Expand Down Expand Up @@ -554,6 +584,8 @@ import simulacrum.typeclass
}

object Foldable {
private val sentinel: Function1[Any, Any] = new scala.runtime.AbstractFunction1[Any, Any]{ def apply(a: Any) = this }

def iterateRight[A, B](iterable: Iterable[A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = {
def loop(it: Iterator[A]): Eval[B] =
Eval.defer(if (it.hasNext) f(it.next, loop(it)) else lb)
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/scala/cats/instances/list.scala
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
override def dropWhile_[A](fa: List[A])(p: A => Boolean): List[A] = fa.dropWhile(p)

override def algebra[A]: Monoid[List[A]] = new kernel.instances.ListMonoid[A]

override def collectFirst[A, B](fa: List[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf)

override def collectFirstSome[A, B](fa: List[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f))

}

implicit def catsStdShowForList[A:Show]: Show[List[A]] =
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/instances/option.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ trait OptionInstances extends cats.kernel.instances.OptionInstances {

override def isEmpty[A](fa: Option[A]): Boolean =
fa.isEmpty

override def collectFirst[A, B](fa: Option[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf)

override def collectFirstSome[A, B](fa: Option[A])(f: A => Option[B]): Option[B] = fa.flatMap(f)
}

implicit def catsStdShowForOption[A](implicit A: Show[A]): Show[Option[A]] =
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/instances/queue.scala
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ trait QueueInstances extends cats.kernel.instances.QueueInstances {

override def algebra[A]: Monoid[Queue[A]] =
new kernel.instances.QueueMonoid[A]

override def collectFirst[A, B](fa: Queue[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf)

override def collectFirstSome[A, B](fa: Queue[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f))
}

implicit def catsStdShowForQueue[A:Show]: Show[Queue[A]] =
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/scala/cats/instances/sortedMap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ trait SortedMapInstances extends SortedMapInstances1 {
A.combineAll(fa.values)

override def toList[A](fa: SortedMap[K, A]): List[A] = fa.values.toList

override def collectFirst[A, B](fa: SortedMap[K, A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(new PartialFunction[(K, A), B] {
override def isDefinedAt(x: (K, A)) = pf.isDefinedAt(x._2)
override def apply(v1: (K, A)) = pf(v1._2)
})

override def collectFirstSome[A, B](fa: SortedMap[K, A])(f: A => Option[B]): Option[B] = collectFirst(fa)(Function.unlift(f))
}

}
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/instances/sortedSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ trait SortedSetInstances extends SortedSetInstances1 {
fa.reduceLeftOption(f)

override def find[A](fa: SortedSet[A])(f: A => Boolean): Option[A] = fa.find(f)

override def collectFirst[A, B](fa: SortedSet[A])(pf: PartialFunction[A, B]): Option[B] =
fa.collectFirst(pf)

override def collectFirstSome[A, B](fa: SortedSet[A])(f: A => Option[B]): Option[B] =
fa.collectFirst(Function.unlift(f))
}

implicit def catsStdShowForSortedSet[A: Show]: Show[SortedSet[A]] = new Show[SortedSet[A]] {
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/instances/stream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ trait StreamInstances extends cats.kernel.instances.StreamInstances {
override def find[A](fa: Stream[A])(f: A => Boolean): Option[A] = fa.find(f)

override def algebra[A]: Monoid[Stream[A]] = new kernel.instances.StreamMonoid[A]

override def collectFirst[A, B](fa: Stream[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf)

override def collectFirstSome[A, B](fa: Stream[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f))
}

implicit def catsStdShowForStream[A: Show]: Show[Stream[A]] =
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/scala/cats/instances/vector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ trait VectorInstances extends cats.kernel.instances.VectorInstances {
override def find[A](fa: Vector[A])(f: A => Boolean): Option[A] = fa.find(f)

override def algebra[A]: Monoid[Vector[A]] = new kernel.instances.VectorMonoid[A]

override def collectFirst[A, B](fa: Vector[A])(pf: PartialFunction[A, B]): Option[B] = fa.collectFirst(pf)

override def collectFirstSome[A, B](fa: Vector[A])(f: A => Option[B]): Option[B] = fa.collectFirst(Function.unlift(f))
}

implicit def catsStdShowForVector[A:Show]: Show[Vector[A]] =
Expand Down
6 changes: 6 additions & 0 deletions laws/src/main/scala/cats/laws/FoldableLaws.scala
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ trait FoldableLaws[F[_]] {
if (buf.nonEmpty || !p(a)) buf += a else buf
}.toList

def collectFirstSome_Ref[A, B](fa: F[A], f: A => Option[B]): IsEq[Option[B]] =
F.collectFirstSome(fa)(f) <-> F.foldLeft(fa, Option.empty[B]){ (ob, a) => if (ob.isDefined) ob else f(a) }

def collectFirst_Ref[A, B](fa: F[A], pf: PartialFunction[A, B]): IsEq[Option[B]] =
F.collectFirst(fa)(pf) <-> F.collectFirstSome(fa)(pf.lift)

def orderedConsistency[A: Eq](x: F[A], y: F[A])(implicit ev: Eq[F[A]]): IsEq[List[A]] =
if (x === y) (F.toList(x) <-> F.toList(y))
else List.empty[A] <-> List.empty[A]
Expand Down
6 changes: 5 additions & 1 deletion laws/src/main/scala/cats/laws/discipline/FoldableTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.scalacheck.Prop._
import org.typelevel.discipline.Laws

import cats.instances.list._
import arbitrary.catsLawsArbitraryForPartialFunction

trait FoldableTests[F[_]] extends Laws {
def laws: FoldableLaws[F]
Expand All @@ -20,6 +21,7 @@ trait FoldableTests[F[_]] extends Laws {
EqA: Eq[A],
EqFA: Eq[F[A]],
EqB: Eq[B],
EqOptionB: Eq[Option[B]],
EqOptionA: Eq[Option[A]]
): RuleSet = {
new DefaultRuleSet(
Expand All @@ -43,7 +45,9 @@ trait FoldableTests[F[_]] extends Laws {
"toList reference" -> forAll(laws.toListRef[A] _),
"filter_ reference" -> forAll(laws.filter_Ref[A] _),
"takeWhile_ reference" -> forAll(laws.takeWhile_Ref[A] _),
"dropWhile_ reference" -> forAll(laws.dropWhile_Ref[A] _)
"dropWhile_ reference" -> forAll(laws.dropWhile_Ref[A] _),
"collectFirstSome reference" -> forAll(laws.collectFirstSome_Ref[A, B] _),
"collectFirst reference" -> forAll(laws.collectFirst_Ref[A, B] _)
)
}
}
Expand Down

0 comments on commit 9325184

Please sign in to comment.