-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add FunctorFilter and TraverseFilter (#2405)
* Add FunctorEmpty and TraverseEmpty * Add TraverseEmpty instance for Chain * Make TraverseEmpty[Chain] serializable I think that the issue here is that it was referencing an instance-level `catsDataInstancesForChain` from an abstract class. By changing it to reference `Chain.catsDataInstancesForChain`, it is a reference to a static member (and therefore doesn't actually need to be serialized). Take my explanation with a grain of salt -- like everyone else on the planet, I don't actually understand Java serialization. But at the end of the day it works :) * Remove conversion to mainline classes * Add traverseFilter <-> traverse consistency law * Make Functor override final * Rename Functor and Traverse Empty to Filter * Add Nested FunctorFilter instance * Address feedback
- Loading branch information
Luka Jacobowitz
authored
Sep 3, 2018
1 parent
77c9319
commit 2f129c6
Showing
33 changed files
with
722 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package cats | ||
|
||
import simulacrum.typeclass | ||
|
||
/** | ||
* `FunctorFilter[F]` allows you to `map` and filter out elements simultaneously. | ||
*/ | ||
@typeclass | ||
trait FunctorFilter[F[_]] extends Serializable { | ||
def functor: 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. | ||
* Equivalent to using `mapFilter` with `identity`. | ||
* | ||
* 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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
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`). | ||
* | ||
* 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 FunctorFilter[F] { | ||
def traverse: Traverse[F] | ||
|
||
final override def functor: Functor[F] = traverse | ||
|
||
/** | ||
* 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[_], A, B](fa: F[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[F[B]] | ||
|
||
/** | ||
* | ||
* 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 mapFilter[A, B](fa: F[A])(f: A => Option[B]): F[B] = | ||
traverseFilter[Id, A, B](fa)(f) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.