Skip to content

Commit

Permalink
Merge pull request #1225 from ceedubs/traverse-filter
Browse files Browse the repository at this point in the history
Add FunctorFilter and TraverseFilter
  • Loading branch information
ceedubs authored Jul 28, 2016
2 parents 106e114 + 5a67c7f commit 5b35e84
Show file tree
Hide file tree
Showing 33 changed files with 547 additions and 75 deletions.
18 changes: 17 additions & 1 deletion core/src/main/scala/cats/Composed.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,30 @@ private[cats] trait ComposedFoldable[F[_], G[_]] extends Foldable[λ[α => F[G[
F.foldRight(fga, lb)((ga, lb) => G.foldRight(ga, lb)(f))
}

private[cats] trait ComposedTraverse[F[_], G[_]] extends Traverse[λ[α => F[G[α]]]] with ComposedFoldable[F, G] with ComposedFunctor[F, G] { outer =>
private[cats] trait ComposedTraverse[F[_], G[_]] extends Traverse[λ[α => F[G[α]]]] with ComposedFoldable[F, G] with ComposedFunctor[F, G] {
def F: Traverse[F]
def G: Traverse[G]

override def traverse[H[_]: Applicative, A, B](fga: F[G[A]])(f: A => H[B]): H[F[G[B]]] =
F.traverse(fga)(ga => G.traverse(ga)(f))
}

private[cats] trait ComposedTraverseFilter[F[_], G[_]] extends TraverseFilter[λ[α => F[G[α]]]] with ComposedTraverse[F, G] {
def F: Traverse[F]
def G: TraverseFilter[G]

override def traverseFilter[H[_]: Applicative, A, B](fga: F[G[A]])(f: A => H[Option[B]]): H[F[G[B]]] =
F.traverse[H, G[A], G[B]](fga)(ga => G.traverseFilter(ga)(f))
}

private[cats] trait ComposedFunctorFilter[F[_], G[_]] extends FunctorFilter[λ[α => F[G[α]]]] with ComposedFunctor[F, G] {
def F: Functor[F]
def G: FunctorFilter[G]

override def mapFilter[A, B](fga: F[G[A]])(f: A => Option[B]): F[G[B]] =
F.map(fga)(G.mapFilter(_)(f))
}

private[cats] trait ComposedReducible[F[_], G[_]] extends Reducible[λ[α => F[G[α]]]] with ComposedFoldable[F, G] { outer =>
def F: Reducible[F]
def G: Reducible[G]
Expand Down
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/Functor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ import simulacrum.typeclass
val G = Functor[G]
}

def composeFilter[G[_]: FunctorFilter]: FunctorFilter[λ[α => F[G[α]]]] =
new ComposedFunctorFilter[F, G] {
val F = self
val G = FunctorFilter[G]
}

override def composeContravariant[G[_]: Contravariant]: Contravariant[λ[α => F[G[α]]]] =
new ComposedCovariantContravariant[F, G] {
val F = self
Expand Down
62 changes: 62 additions & 0 deletions core/src/main/scala/cats/FunctorFilter.scala
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)
}
8 changes: 4 additions & 4 deletions core/src/main/scala/cats/MonadFilter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]

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))
}
6 changes: 6 additions & 0 deletions core/src/main/scala/cats/Traverse.scala
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ import simulacrum.typeclass
val G = Traverse[G]
}

def composeFilter[G[_]: TraverseFilter]: TraverseFilter[λ[α => F[G[α]]]] =
new ComposedTraverseFilter[F, G] {
val F = self
val G = TraverseFilter[G]
}

override def map[A, B](fa: F[A])(f: A => B): F[B] =
traverse[Id, A, B](fa)(f)
}
65 changes: 65 additions & 0 deletions core/src/main/scala/cats/TraverseFilter.scala
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(_)))
}
12 changes: 9 additions & 3 deletions core/src/main/scala/cats/data/Const.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ final case class Const[A, B](getConst: A) {
def combine(that: Const[A, B])(implicit A: Semigroup[A]): Const[A, B] =
Const(A.combine(getConst, that.getConst))

def traverseFilter[F[_], C](f: B => F[Option[C]])(implicit F: Applicative[F]): F[Const[A, C]] =
F.pure(retag[C])

def traverse[F[_], C](f: B => F[C])(implicit F: Applicative[F]): F[Const[A, C]] =
F.pure(retag[C])

Expand Down Expand Up @@ -53,13 +56,16 @@ private[data] sealed abstract class ConstInstances extends ConstInstances0 {
fa.retag[B]
}

implicit def catsDataTraverseForConst[C]: Traverse[Const[C, ?]] = new Traverse[Const[C, ?]] {
def traverse[G[_]: Applicative, A, B](fa: Const[C, A])(f: A => G[B]): G[Const[C, B]] =
fa.traverse(f)
implicit def catsDataTraverseFilterForConst[C]: TraverseFilter[Const[C, ?]] = new TraverseFilter[Const[C, ?]] {
def traverseFilter[G[_]: Applicative, A, B](fa: Const[C, A])(f: A => G[Option[B]]): G[Const[C, B]] =
fa.traverseFilter(f)

def foldLeft[A, B](fa: Const[C, A], b: B)(f: (B, A) => B): B = b

def foldRight[A, B](fa: Const[C, A], lb: Eval[B])(f: (A, Eval[B]) => Eval[B]): Eval[B] = lb

override def traverse[G[_]: Applicative, A, B](fa: Const[C, A])(f: A => G[B]): G[Const[C, B]] =
fa.traverse(f)
}

implicit def catsDataMonoidForConst[A: Monoid, B]: Monoid[Const[A, B]] = new Monoid[Const[A, B]]{
Expand Down
39 changes: 37 additions & 2 deletions core/src/main/scala/cats/data/Nested.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,18 @@ final case class Nested[F[_], G[_], A](value: F[G[A]])

object Nested extends NestedInstances

private[data] sealed abstract class NestedInstances extends NestedInstances1 {
private[data] sealed abstract class NestedInstances extends NestedInstances0 {
implicit def catsDataEqForNested[F[_], G[_], A](implicit FGA: Eq[F[G[A]]]): Eq[Nested[F, G, A]] =
FGA.on(_.value)

implicit def catsDataTraverseFilterForNested[F[_]: Traverse, G[_]: TraverseFilter]: TraverseFilter[Nested[F, G, ?]] =
new NestedTraverseFilter[F, G] {
val FG: TraverseFilter[λ[α => F[G[α]]]] = Traverse[F].composeFilter[G]
}
}

private[data] sealed abstract class NestedInstances0 extends NestedInstances1 {

implicit def catsDataTraverseForNested[F[_]: Traverse, G[_]: Traverse]: Traverse[Nested[F, G, ?]] =
new NestedTraverse[F, G] {
val FG: Traverse[λ[α => F[G[α]]]] = Traverse[F].compose[G]
Expand Down Expand Up @@ -118,7 +126,14 @@ private[data] sealed abstract class NestedInstances8 extends NestedInstances9 {
}
}

private[data] sealed abstract class NestedInstances9 {
private[data] sealed abstract class NestedInstances9 extends NestedInstances10 {
implicit def catsDataFunctorFilterForNested[F[_]: Functor, G[_]: FunctorFilter]: FunctorFilter[Nested[F, G, ?]] =
new NestedFunctorFilter[F, G] {
val FG: FunctorFilter[λ[α => F[G[α]]]] = Functor[F].composeFilter[G]
}
}

private[data] sealed abstract class NestedInstances10 {
implicit def catsDataInvariantForNestedContravariant[F[_]: Invariant, G[_]: Contravariant]: Invariant[Nested[F, G, ?]] =
new NestedInvariant[F, G] {
val FG: Invariant[λ[α => F[G[α]]]] = Invariant[F].composeContravariant[G]
Expand All @@ -139,6 +154,26 @@ private[data] trait NestedFunctor[F[_], G[_]] extends Functor[Nested[F, G, ?]] w
Nested(FG.map(fga.value)(f))
}

private[data] trait NestedFunctorFilter[F[_], G[_]] extends FunctorFilter[Nested[F, G, ?]] with NestedFunctor[F, G] {
override def FG: FunctorFilter[λ[α => F[G[α]]]]

override def mapFilter[A, B](fga: Nested[F, G, A])(f: A => Option[B]): Nested[F, G, B] =
Nested(FG.mapFilter(fga.value)(f))

override def collect[A, B](fga: Nested[F, G, A])(f: PartialFunction[A, B]): Nested[F, G, B] =
Nested(FG.collect(fga.value)(f))

override def filter[A](fga: Nested[F, G, A])(f: A => Boolean): Nested[F, G, A] =
Nested(FG.filter(fga.value)(f))
}

private[data] trait NestedTraverseFilter[F[_], G[_]] extends TraverseFilter[Nested[F, G, ?]] with NestedFunctorFilter[F, G] with NestedTraverse[F, G] {
override def FG: TraverseFilter[λ[α => F[G[α]]]]

override def traverseFilter[H[_]: Applicative, A, B](fga: Nested[F, G, A])(f: A => H[Option[B]]): H[Nested[F, G, B]] =
Applicative[H].map(FG.traverseFilter(fga.value)(f))(Nested(_))
}

private[data] trait NestedApply[F[_], G[_]] extends Apply[Nested[F, G, ?]] with NestedFunctor[F, G] {
override def FG: Apply[λ[α => F[G[α]]]]

Expand Down
22 changes: 17 additions & 5 deletions core/src/main/scala/cats/data/OptionT.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def semiflatMap[B](f: A => F[B])(implicit F: Monad[F]): OptionT[F, B] =
flatMap(a => OptionT.liftF(f(a)))

def mapFilter[B](f: A => Option[B])(implicit F: Functor[F]): OptionT[F, B] =
OptionT(F.map(value)(_.flatMap(f)))

def flatMap[B](f: A => OptionT[F, B])(implicit F: Monad[F]): OptionT[F, B] =
flatMapF(a => f(a).value)

Expand Down Expand Up @@ -99,6 +102,9 @@ final case class OptionT[F[_], A](value: F[Option[A]]) {
def ===(that: OptionT[F, A])(implicit eq: Eq[F[Option[A]]]): Boolean =
eq.eqv(value, that.value)

def traverseFilter[G[_], B](f: A => G[Option[B]])(implicit F: Traverse[F], G: Applicative[G]): G[OptionT[F, B]] =
G.map(F.composeFilter(optionInstance).traverseFilter(value)(f))(OptionT.apply)

def traverse[G[_], B](f: A => G[B])(implicit F: Traverse[F], G: Applicative[G]): G[OptionT[F, B]] =
G.map(F.compose(optionInstance).traverse(value)(f))(OptionT.apply)

Expand Down Expand Up @@ -219,19 +225,22 @@ private[data] sealed trait OptionTInstances1 extends OptionTInstances2 {
}

private[data] sealed trait OptionTInstances2 extends OptionTInstances3 {
implicit def catsDataTraverseForOptionT[F[_]](implicit F0: Traverse[F]): Traverse[OptionT[F, ?]] =
new OptionTTraverse[F] { implicit val F = F0 }
implicit def catsDataTraverseForOptionT[F[_]](implicit F0: Traverse[F]): TraverseFilter[OptionT[F, ?]] =
new OptionTTraverseFilter[F] { implicit val F = F0 }
}

private[data] sealed trait OptionTInstances3 {
implicit def catsDataFunctorForOptionT[F[_]](implicit F0: Functor[F]): Functor[OptionT[F, ?]] =
implicit def catsDataFunctorFilterForOptionT[F[_]](implicit F0: Functor[F]): FunctorFilter[OptionT[F, ?]] =
new OptionTFunctor[F] { implicit val F = F0 }
}

private[data] trait OptionTFunctor[F[_]] extends Functor[OptionT[F, ?]] {
private[data] trait OptionTFunctor[F[_]] extends FunctorFilter[OptionT[F, ?]] {
implicit def F: Functor[F]

override def map[A, B](fa: OptionT[F, A])(f: A => B): OptionT[F, B] = fa.map(f)

override def mapFilter[A, B](fa: OptionT[F, A])(f: A => Option[B]): OptionT[F, B] =
fa.mapFilter(f)
}

private[data] trait OptionTMonad[F[_]] extends Monad[OptionT[F, ?]] {
Expand Down Expand Up @@ -273,9 +282,12 @@ private[data] trait OptionTFoldable[F[_]] extends Foldable[OptionT[F, ?]] {
fa.foldRight(lb)(f)
}

private[data] sealed trait OptionTTraverse[F[_]] extends Traverse[OptionT[F, ?]] with OptionTFoldable[F] {
private[data] sealed trait OptionTTraverseFilter[F[_]] extends TraverseFilter[OptionT[F, ?]] with OptionTFoldable[F] {
implicit def F: Traverse[F]

def traverseFilter[G[_]: Applicative, A, B](fa: OptionT[F, A])(f: A => G[Option[B]]): G[OptionT[F, B]] =
fa traverseFilter f

override def traverse[G[_]: Applicative, A, B](fa: OptionT[F, A])(f: A => G[B]): G[OptionT[F, B]] =
fa traverse f
}
Expand Down
13 changes: 10 additions & 3 deletions core/src/main/scala/cats/instances/list.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import cats.data.Xor

trait ListInstances extends cats.kernel.instances.ListInstances {

implicit val catsStdInstancesForList: Traverse[List] with MonadCombine[List] with MonadRec[List] with CoflatMap[List] =
new Traverse[List] with MonadCombine[List] with MonadRec[List] with CoflatMap[List] {
implicit val catsStdInstancesForList: TraverseFilter[List] with MonadCombine[List] with MonadRec[List] with CoflatMap[List] =
new TraverseFilter[List] with MonadCombine[List] with MonadRec[List] with CoflatMap[List] {

def empty[A]: List[A] = Nil

Expand Down Expand Up @@ -63,7 +63,12 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
Eval.defer(loop(fa))
}

def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] =
def traverseFilter[G[_], A, B](fa: List[A])(f: A => G[Option[B]])(implicit G: Applicative[G]): G[List[B]] =
foldRight[A, G[List[B]]](fa, Always(G.pure(List.empty))){ (a, lglb) =>
G.map2Eval(f(a), lglb)((ob, l) => ob.fold(l)(_ :: l))
}.value

override def traverse[G[_], A, B](fa: List[A])(f: A => G[B])(implicit G: Applicative[G]): G[List[B]] =
foldRight[A, G[List[B]]](fa, Always(G.pure(List.empty))){ (a, lglb) =>
G.map2Eval(f(a), lglb)(_ :: _)
}.value
Expand All @@ -75,6 +80,8 @@ trait ListInstances extends cats.kernel.instances.ListInstances {
fa.forall(p)

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

override def filter[A](fa: List[A])(f: A => Boolean): List[A] = fa.filter(f)
}

implicit def catsStdShowForList[A:Show]: Show[List[A]] =
Expand Down
Loading

0 comments on commit 5b35e84

Please sign in to comment.