-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Add FunctorFilter and TraverseFilter #1225
Changes from all commits
6d6eb79
e04fc46
b0af1d6
c4b55ca
7e5b0f4
947594c
bf12418
5a67c7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package cats | ||
|
||
import simulacrum.typeclass | ||
|
||
@typeclass trait FunctorFilter[F[_]] extends Functor[F] { | ||
|
||
/** | ||
* A combined [[map]] and [[filter]]. Filtering is handled via `Option` | ||
* instead of `Boolean` such that the output type `B` can be different than | ||
* the input type `A`. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> val m: Map[Int, String] = Map(1 -> "one", 3 -> "three") | ||
* scala> val l: List[Int] = List(1, 2, 3, 4) | ||
* scala> def asString(i: Int): Option[String] = m.get(i) | ||
* scala> l.mapFilter(i => m.get(i)) | ||
* res0: List[String] = List(one, three) | ||
* }}} | ||
*/ | ||
def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] | ||
|
||
/** | ||
* Similar to [[mapFilter]] but uses a partial function instead of a function | ||
* that returns an `Option`. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> val l: List[Int] = List(1, 2, 3, 4) | ||
* scala> FunctorFilter[List].collect(l){ | ||
* | case 1 => "one" | ||
* | case 3 => "three" | ||
* | } | ||
* res0: List[String] = List(one, three) | ||
* }}} | ||
*/ | ||
def collect[A, B](fa: F[A])(f: PartialFunction[A, B]): F[B] = | ||
mapFilter(fa)(f.lift) | ||
|
||
/** | ||
* "Flatten" out a structure by collapsing `Option`s. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> val l: List[Option[Int]] = List(Some(1), None, Some(3), None) | ||
* scala> l.flattenOption | ||
* res0: List[Int] = List(1, 3) | ||
* }}} | ||
*/ | ||
def flattenOption[A](fa: F[Option[A]]): F[A] = mapFilter(fa)(identity) | ||
|
||
/** | ||
* Apply a filter to a structure such that the output structure contains all | ||
* `A` elements in the input structure that satisfy the predicate `f` but none | ||
* that don't. | ||
*/ | ||
def filter[A](fa: F[A])(f: A => Boolean): F[A] = | ||
mapFilter(fa)(a => if (f(a)) Some(a) else None) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,15 +4,15 @@ import simulacrum.typeclass | |
|
||
/** | ||
* a Monad equipped with an additional method which allows us to | ||
* create an "Empty" value for the Monad (for whatever "empty" makes | ||
* create an "empty" value for the Monad (for whatever "empty" makes | ||
* sense for that particular monad). This is of particular interest to | ||
* us since it allows us to add a `filter` method to a Monad, which is | ||
* used when pattern matching or using guards in for comprehensions. | ||
*/ | ||
@typeclass trait MonadFilter[F[_]] extends Monad[F] { | ||
@typeclass trait MonadFilter[F[_]] extends Monad[F] with FunctorFilter[F] { | ||
|
||
def empty[A]: F[A] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like a FunctorFilter had to have an empty value too. We could just filter everything from any value and get there, no? Seems like empty might almost be there. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That's true, but you would have to have a value to start with. With I was thinking about whether the I think that if we were to add an At this point I haven't added |
||
|
||
def filter[A](fa: F[A])(f: A => Boolean): F[A] = | ||
flatMap(fa)(a => if (f(a)) pure(a) else empty[A]) | ||
override def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] = | ||
flatMap(fa)(a => f(a).fold(empty[B])(pure)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package cats | ||
|
||
import simulacrum.typeclass | ||
|
||
/** | ||
* `TraverseFilter`, also known as `Witherable`, represents list-like structures | ||
* that can essentially have a [[traverse]] and a [[filter]] applied as a single | ||
* combined operation ([[traverseFilter]]). | ||
* | ||
* Must obey the laws defined in cats.laws.TraverseFilterLaws. | ||
* | ||
* Based on Haskell's [[https://hackage.haskell.org/package/witherable-0.1.3.3/docs/Data-Witherable.html Data.Witherable]] | ||
*/ | ||
@typeclass trait TraverseFilter[F[_]] extends Traverse[F] with FunctorFilter[F] { self => | ||
|
||
/** | ||
* A combined [[traverse]] and [[filter]]. Filtering is handled via `Option` | ||
* instead of `Boolean` such that the output type `B` can be different than | ||
* the input type `A`. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> val m: Map[Int, String] = Map(1 -> "one", 3 -> "three") | ||
* scala> val l: List[Int] = List(1, 2, 3, 4) | ||
* scala> def asString(i: Int): Eval[Option[String]] = Now(m.get(i)) | ||
* scala> val result: Eval[List[String]] = l.traverseFilter(asString) | ||
* scala> result.value | ||
* res0: List[String] = List(one, three) | ||
* }}} | ||
*/ | ||
def traverseFilter[G[_]: Applicative, A, B](fa: F[A])(f: A => G[Option[B]]): G[F[B]] | ||
|
||
override def mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] = | ||
traverseFilter[Id, A, B](fa)(f) | ||
|
||
/** | ||
* | ||
* Filter values inside a `G` context. | ||
* | ||
* This is a generalized version of Haskell's [[http://hackage.haskell.org/package/base-4.9.0.0/docs/Control-Monad.html#v:filterM filterM]]. | ||
* [[http://stackoverflow.com/questions/28872396/haskells-filterm-with-filterm-x-true-false-1-2-3 This StackOverflow question]] about `filterM` may be helpful in understanding how it behaves. | ||
* | ||
* Example: | ||
* {{{ | ||
* scala> import cats.implicits._ | ||
* scala> val l: List[Int] = List(1, 2, 3, 4) | ||
* scala> def odd(i: Int): Eval[Boolean] = Now(i % 2 == 1) | ||
* scala> val res: Eval[List[Int]] = l.filterA(odd) | ||
* scala> res.value | ||
* res0: List[Int] = List(1, 3) | ||
* | ||
* scala> List(1, 2, 3).filterA(_ => List(true, false)) | ||
* res1: List[List[Int]] = List(List(1, 2, 3), List(1, 2), List(1, 3), List(1), List(2, 3), List(2), List(3), List()) | ||
* }}} | ||
*/ | ||
def filterA[G[_], A](fa: F[A])(f: A => G[Boolean])(implicit G: Applicative[G]): G[F[A]] = | ||
traverseFilter(fa)(a => G.map(f(a))(if (_) Some(a) else None)) | ||
|
||
override def filter[A](fa: F[A])(f: A => Boolean): F[A] = | ||
filterA[Id, A](fa)(f) | ||
|
||
override def traverse[G[_], A, B](fa: F[A])(f: A => G[B])(implicit G: Applicative[G]): G[F[B]] = | ||
traverseFilter(fa)(a => G.map(f(a))(Some(_))) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it is worth thinking about a typeclass for mapConcat? The law would be similar to the one here. Collections support this but also distributed compute libraries like spark and scalding.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnynek what's
mapConcat
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def mapConcat[A, B](fa: F[A])(f: A => Iterable[B]): F[B]
something like that.For instance there is no
FunctorMapConcat[Option]
but there isFunctorMapConcat[Vector]
and in the case of scaldingFunctorMapConcat[TypedPipe]
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, when you think about it, it might be something like this:
And we are just talking about various
KleisliArrows
here.Or maybe we mean something like:
It is not clear what the right level of generalization is. It seems to me
A => M[B]
where you haveFoldable[M]
is pretty generally useful. Maybe that is the thing you want instead of mapConcat:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was some similar discussion a long time ago here. I wonder what the valid laws for this construct would be and whether you would run into issues where just working with
Foldable
would mean implementations would tend to be inefficient.@johnynek were you suggesting something like this instead of
FunctorFilter
orTraverseFilter
? I would tend to think that something like this should be in addition to these type classes. As you've mentioned you couldn't have an instance of this type class forOption
orOptionT
, which I think is a pretty big limitation, since they are common types to callfilter
orcollect
on. I also feel like there is some benefit to knowing these methods can never make your structure grow (they can only reduce it or leave it the same); however this is just a fuzzy feeling and not something that I can confidently say is a useful property.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was mostly thinking out loud to figure out what this should be.