Skip to content
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

added collectFirst and collectFirstSome to Foldable #2030

Merged
merged 12 commits into from
Nov 21, 2017
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] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth mentioning that a more efficient implementation is often possible? We do this on size and foldM, but I suppose it would be easy to get carried away with this. I don't feel strongly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My battery is going to die before I can run the tests, but I think this might be right:

    foldRight(fa, Eval.now(Option.empty[B])) { (a, lb) =>
      var result: Eval[Option[B]] = lb
      pf.runWith(b => result = Eval.now(Some(b)))(a)
      result
    }.value

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the TraversableOnce trick in foldRight form. TraversableOnce claims it's faster than runWith, but I have not benchmarked it in this context. Tests pass even after I remove the overrides. sentinel could be moved to a private member of the companion.

    val sentinel: Function1[A, Any] = new scala.runtime.AbstractFunction1[A, Any]{ def apply(a: A) = this }
    foldRight(fa, Eval.now(Option.empty[B])) { (a, lb) =>
      val x = pf.applyOrElse(a, sentinel)
      if (x.asInstanceOf[AnyRef] ne sentinel) Eval.now(Some(x.asInstanceOf[B]))
      else lb
    }.value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks a lot @rossabaker , I made the change.

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