Skip to content

Commit 0279e2f

Browse files
add withFilter() method for efficient compilation for-comprehensions with if guard
1 parent bc21eb0 commit 0279e2f

2 files changed

Lines changed: 105 additions & 65 deletions

File tree

core/src/main/scala/cats/FunctorFilter.scala

Lines changed: 77 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -23,83 +23,84 @@ package cats
2323

2424
import scala.collection.immutable.{Queue, Seq, SortedMap}
2525

26-
/**
27-
* `FunctorFilter[F]` allows you to `map` and filter out elements simultaneously.
28-
*/
26+
/** `FunctorFilter[F]` allows you to `map` and filter out elements
27+
* simultaneously.
28+
*/
2929

3030
trait FunctorFilter[F[_]] extends Serializable {
3131
def functor: Functor[F]
3232

33-
/**
34-
* A combined `map` and `filter`. Filtering is handled via `Option`
35-
* instead of `Boolean` such that the output type `B` can be different than
36-
* the input type `A`.
37-
*
38-
* Example:
39-
* {{{
40-
* scala> import cats.syntax.all._
41-
* scala> val m: Map[Int, String] = Map(1 -> "one", 3 -> "three")
42-
* scala> val l: List[Int] = List(1, 2, 3, 4)
43-
* scala> def asString(i: Int): Option[String] = m.get(i)
44-
* scala> l.mapFilter(asString)
45-
* res0: List[String] = List(one, three)
46-
* }}}
47-
*/
33+
/** A combined `map` and `filter`. Filtering is handled via `Option` instead
34+
* of `Boolean` such that the output type `B` can be different than the input
35+
* type `A`.
36+
*
37+
* Example:
38+
* {{{
39+
* scala> import cats.syntax.all._
40+
* scala> val m: Map[Int, String] = Map(1 -> "one", 3 -> "three")
41+
* scala> val l: List[Int] = List(1, 2, 3, 4)
42+
* scala> def asString(i: Int): Option[String] = m.get(i)
43+
* scala> l.mapFilter(asString)
44+
* res0: List[String] = List(one, three)
45+
* }}}
46+
*/
4847
def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B]
4948

50-
/**
51-
* Similar to [[mapFilter]] but uses a partial function instead of a function
52-
* that returns an `Option`.
53-
*
54-
* Example:
55-
* {{{
56-
* scala> import cats.syntax.all._
57-
* scala> val l: List[Int] = List(1, 2, 3, 4)
58-
* scala> FunctorFilter[List].collect(l){
59-
* | case 1 => "one"
60-
* | case 3 => "three"
61-
* | }
62-
* res0: List[String] = List(one, three)
63-
* }}}
64-
*/
49+
/** Similar to [[mapFilter]] but uses a partial function instead of a function
50+
* that returns an `Option`.
51+
*
52+
* Example:
53+
* {{{
54+
* scala> import cats.syntax.all._
55+
* scala> val l: List[Int] = List(1, 2, 3, 4)
56+
* scala> FunctorFilter[List].collect(l){
57+
* | case 1 => "one"
58+
* | case 3 => "three"
59+
* | }
60+
* res0: List[String] = List(one, three)
61+
* }}}
62+
*/
6563
def collect[A, B](fa: F[A])(f: PartialFunction[A, B]): F[B] =
6664
mapFilter(fa)(f.lift)
6765

68-
/**
69-
* "Flatten" out a structure by collapsing `Option`s.
70-
* Equivalent to using `mapFilter` with `identity`.
71-
*
72-
* Example:
73-
* {{{
74-
* scala> import cats.syntax.all._
75-
* scala> val l: List[Option[Int]] = List(Some(1), None, Some(3), None)
76-
* scala> l.flattenOption
77-
* res0: List[Int] = List(1, 3)
78-
* }}}
79-
*/
66+
/** "Flatten" out a structure by collapsing `Option`s. Equivalent to using
67+
* `mapFilter` with `identity`.
68+
*
69+
* Example:
70+
* {{{
71+
* scala> import cats.syntax.all._
72+
* scala> val l: List[Option[Int]] = List(Some(1), None, Some(3), None)
73+
* scala> l.flattenOption
74+
* res0: List[Int] = List(1, 3)
75+
* }}}
76+
*/
8077
def flattenOption[A](fa: F[Option[A]]): F[A] =
8178
mapFilter(fa)(identity)
8279

83-
/**
84-
* Apply a filter to a structure such that the output structure contains all
85-
* `A` elements in the input structure that satisfy the predicate `f` but none
86-
* that don't.
87-
*/
80+
/** Apply a filter to a structure such that the output structure contains all
81+
* `A` elements in the input structure that satisfy the predicate `f` but
82+
* none that don't.
83+
*/
8884
def filter[A](fa: F[A])(f: A => Boolean): F[A] =
8985
mapFilter(fa)(a => if (f(a)) Some(a) else None)
9086

91-
/**
92-
* Apply a filter to a structure such that the output structure contains all
93-
* `A` elements in the input structure that do not satisfy the predicate `f`.
94-
*/
87+
/** Alias for [[filter]].
88+
*/
89+
def withFilter[A](fa: F[A])(f: A => Boolean): F[A] =
90+
filter(fa)(f)
91+
92+
/** Apply a filter to a structure such that the output structure contains all
93+
* `A` elements in the input structure that do not satisfy the predicate `f`.
94+
*/
9595
def filterNot[A](fa: F[A])(f: A => Boolean): F[A] =
9696
mapFilter(fa)(Some(_).filterNot(f))
9797
}
9898

9999
object FunctorFilter extends ScalaVersionSpecificTraverseFilterInstances with FunctorFilterInstances0 {
100100
implicit def catsTraverseFilterForOption: TraverseFilter[Option] =
101101
cats.instances.option.catsStdTraverseFilterForOption
102-
implicit def catsTraverseFilterForList: TraverseFilter[List] = cats.instances.list.catsStdTraverseFilterForList
102+
implicit def catsTraverseFilterForList: TraverseFilter[List] =
103+
cats.instances.list.catsStdTraverseFilterForList
103104
implicit def catsTraverseFilterForVector: TraverseFilter[Vector] =
104105
cats.instances.vector.catsStdTraverseFilterForVector
105106
implicit def catsFunctorFilterForMap[K]: FunctorFilter[Map[K, *]] =
@@ -109,14 +110,17 @@ object FunctorFilter extends ScalaVersionSpecificTraverseFilterInstances with Fu
109110
implicit def catsTraverseFilterForQueue: TraverseFilter[Queue] =
110111
cats.instances.queue.catsStdTraverseFilterForQueue
111112

112-
/**
113-
* Summon an instance of [[FunctorFilter]] for `F`.
114-
*/
115-
@inline def apply[F[_]](implicit instance: FunctorFilter[F]): FunctorFilter[F] = instance
113+
/** Summon an instance of [[FunctorFilter]] for `F`.
114+
*/
115+
@inline def apply[F[_]](implicit
116+
instance: FunctorFilter[F]
117+
): FunctorFilter[F] = instance
116118

117119
@deprecated("Use cats.syntax object imports", "2.2.0")
118120
object ops {
119-
implicit def toAllFunctorFilterOps[F[_], A](target: F[A])(implicit tc: FunctorFilter[F]): AllOps[F, A] {
121+
implicit def toAllFunctorFilterOps[F[_], A](
122+
target: F[A]
123+
)(implicit tc: FunctorFilter[F]): AllOps[F, A] {
120124
type TypeClassType = FunctorFilter[F]
121125
} =
122126
new AllOps[F, A] {
@@ -129,16 +133,23 @@ object FunctorFilter extends ScalaVersionSpecificTraverseFilterInstances with Fu
129133
type TypeClassType <: FunctorFilter[F]
130134
def self: F[A]
131135
val typeClassInstance: TypeClassType
132-
def mapFilter[B](f: A => Option[B]): F[B] = typeClassInstance.mapFilter[A, B](self)(f)
133-
def collect[B](f: PartialFunction[A, B]): F[B] = typeClassInstance.collect[A, B](self)(f)
136+
def mapFilter[B](f: A => Option[B]): F[B] =
137+
typeClassInstance.mapFilter[A, B](self)(f)
138+
def collect[B](f: PartialFunction[A, B]): F[B] =
139+
typeClassInstance.collect[A, B](self)(f)
134140
def flattenOption[B](implicit ev$1: A <:< Option[B]): F[B] =
135141
typeClassInstance.flattenOption[B](self.asInstanceOf[F[Option[B]]])
136142
def filter(f: A => Boolean): F[A] = typeClassInstance.filter[A](self)(f)
137-
def filterNot(f: A => Boolean): F[A] = typeClassInstance.filterNot[A](self)(f)
143+
def filterNot(f: A => Boolean): F[A] =
144+
typeClassInstance.filterNot[A](self)(f)
145+
def withFilter(f: A => Boolean): F[A] =
146+
typeClassInstance.withFilter[A](self)(f)
138147
}
139148
trait AllOps[F[_], A] extends Ops[F, A]
140149
trait ToFunctorFilterOps extends Serializable {
141-
implicit def toFunctorFilterOps[F[_], A](target: F[A])(implicit tc: FunctorFilter[F]): Ops[F, A] {
150+
implicit def toFunctorFilterOps[F[_], A](
151+
target: F[A]
152+
)(implicit tc: FunctorFilter[F]): Ops[F, A] {
142153
type TypeClassType = FunctorFilter[F]
143154
} =
144155
new Ops[F, A] {
@@ -154,6 +165,7 @@ object FunctorFilter extends ScalaVersionSpecificTraverseFilterInstances with Fu
154165

155166
trait FunctorFilterInstances0 {
156167

157-
implicit def catsTraverseFilterForSeq: TraverseFilter[Seq] = cats.instances.seq.catsStdTraverseFilterForSeq
168+
implicit def catsTraverseFilterForSeq: TraverseFilter[Seq] =
169+
cats.instances.seq.catsStdTraverseFilterForSeq
158170

159171
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package cats.tests
2+
3+
import cats.syntax.all.*
4+
import cats.Functor
5+
import cats.FunctorFilter
6+
7+
class FunctorFilterSuite extends CatsSuite {
8+
9+
test("withFilter alias allows for-comprehensions with guards") {
10+
// Explicitly use FunctorFilter to provide the withFilter method
11+
// to prove that for-comprehension guards work on any FunctorFilter
12+
def filterEvens[F[_]: FunctorFilter, A](fa: F[A])(implicit
13+
ev: A =:= Int
14+
): F[A] = {
15+
implicit val F: Functor[F] = FunctorFilter[F].functor
16+
for {
17+
a <- fa
18+
if ev(a) % 2 == 0
19+
} yield a
20+
}
21+
22+
val list = List(1, 2, 3, 4, 5)
23+
val evens = filterEvens(list)
24+
25+
assertEquals(evens, List(2, 4))
26+
}
27+
28+
}

0 commit comments

Comments
 (0)