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 Reducible[_] type class. #298

Merged
merged 8 commits into from
May 21, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 24 additions & 10 deletions core/src/main/scala/cats/Fold.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cats

/**
* Fold is designed to allow laziness/short-circuiting in foldLazy.
* Fold is designed to allow laziness/short-circuiting in foldRight.
*
* It is a sum type that has three possible subtypes:
*
Expand All @@ -10,14 +10,14 @@ package cats
* - `Pass`: continue the fold, with no computation for this step.
*
* The meaning of these types can be made more clear with an example
* of the foldLazy method in action. Here's a method to count how many
* of the foldRight method in action. Here's a method to count how many
* elements appear in a list before the value 3:
*
* {{{
* def f(n: Int): Fold[Int] =
* if (n == 3) Fold.Return(0) else Fold.Continue(_ + 1)
*
* val count: Lazy[Int] = List(1,2,3,4).foldLazy(Lazy(0))(f)
* val count: Lazy[Int] = List(1,2,3,4).foldRight(Lazy(0))(f)
* }}}
*
* When we call `count.value`, the following occurs:
Expand All @@ -38,7 +38,7 @@ package cats
*
* {{{
* val found: Lazy[Boolean] =
* Stream.from(0).foldLazy(Lazy(false)) { n =>
* Stream.from(0).foldRight(Lazy(false)) { n =>
* if (n == 77) Fold.Return(true) else Fold.Pass
* }
* }}}
Expand All @@ -48,7 +48,7 @@ package cats
*
* {{{
* val sum: Lazy[Double] =
* numbers.foldLazy(Lazy(0.0)) { n =>
* numbers.foldRight(Lazy(0.0)) { n =>
* if (n < 0) Fold.Return(0.0) else Fold.Continue(n + _)
* }
* }}}
Expand All @@ -58,16 +58,16 @@ package cats
*
* {{{
* val count: Lazy[Long] =
* Stream.from(0).foldLazy(Lazy(0L)) { _ =>
* Stream.from(0).foldRight(Lazy(0L)) { _ =>
* Fold.Continue(_ + 1L)
* }
* }}}
*
* You can even implement foldLeft in terms of foldLazy (!):
* You can even implement foldLeft in terms of foldRight (!):
*
* {{{
* def foldl[A, B](as: List[A], b: B)(f: (B, A) => B): B =
* as.foldLazy(Lazy((b: B) => b)) { a =>
* as.foldRight(Lazy((b: B) => b)) { a =>
* Fold.Continue(g => (b: B) => g(f(b, a)))
* }.value(b)
* }}}
Expand All @@ -76,7 +76,21 @@ package cats
* not stack-safe.)
*/
sealed abstract class Fold[A] extends Product with Serializable {
import Fold.{Return, Continue}
import Fold.{Return, Continue, Pass}

def imap[B](f: A => B)(g: B => A): Fold[B] =
this match {
case Return(a) => Return(f(a))
case Continue(h) => Continue(b => f(g(b)))
case _ => Pass
}

def compose(f: A => A): Fold[A] =
this match {
case Return(a) => Return(f(a))
case Continue(g) => Continue(f andThen g)
case _ => Continue(f)
}

def complete(la: Lazy[A]): A =
this match {
Expand All @@ -92,7 +106,7 @@ object Fold {
* Return signals that the "rest" of a fold can be ignored.
*
* Crucially, the `a` value here is not necessarily the value that
* will be returned from foldLazy, but instead it is the value that
* will be returned from foldRight, but instead it is the value that
* will be returned to the previous functions provided by any Continue
* instances.
*/
Expand Down
84 changes: 55 additions & 29 deletions core/src/main/scala/cats/Foldable.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package cats

import scala.collection.mutable
import simulacrum._

/**
Expand All @@ -14,7 +15,9 @@ import simulacrum._
* Foldable[F] is implemented in terms of two basic methods:
*
* - `foldLeft(fa, b)(f)` eagerly folds `fa` from left-to-right.
* - `foldLazy(fa, b)(f)` lazily folds `fa` from right-to-left.
* - `foldRight(fa, b)(f)` lazily folds `fa` from right-to-left.
*
* (Actually `foldRight` is implemented in terms of `partialFold`.)
*
* Beyond these it provides many other useful methods related to
* folding over F[A] values.
Expand All @@ -38,25 +41,28 @@ import simulacrum._
* For more detailed information about how this method works see the
* documentation for `Fold[_]`.
*/
def foldLazy[A, B](fa: F[A], lb: Lazy[B])(f: A => Fold[B]): Lazy[B] =
def foldRight[A, B](fa: F[A], lb: Lazy[B])(f: A => Fold[B]): Lazy[B] =
Lazy(partialFold[A, B](fa)(f).complete(lb))

/**
* Low-level method that powers `foldLazy`.
* Low-level method that powers `foldRight`.
*/
def partialFold[A, B](fa: F[A])(f: A => Fold[B]): Fold[B]

/**
* Right associative fold on 'F' using the function 'f'.
*
* The default implementation is written in terms of
* `foldLazy`. Most instances will want to override this method for
* performance reasons.
*/
def foldRight[A, B](fa: F[A], b: B)(f: (A, B) => B): B =
foldLazy(fa, Lazy.eager(b)) { a =>
Fold.Continue(b => f(a, b))
}.value

def reduceLeftToOption[A, B](fa: F[A])(f: A => B)(g: (B, A) => B): Option[B] =
foldLeft(fa, Option.empty[B]) {
case (Some(b), a) => Some(g(b, a))
case (None, a) => Some(f(a))
}

def reduceRightToOption[A, B](fa: F[A])(f: A => B)(g: A => Fold[B]): Lazy[Option[B]] =
foldRight(fa, Lazy.eager(Option.empty[B])) { a =>
Fold.Continue {
case None => Some(f(a))
case Some(b) => Some(g(a).complete(Lazy.eager(f(a))))
}
}

/**
* Fold implemented using the given Monoid[A] instance.
Expand All @@ -71,9 +77,7 @@ import simulacrum._
* combining them using the given `Monoid[B]` instance.
*/
def foldMap[A, B](fa: F[A])(f: A => B)(implicit B: Monoid[B]): B =
foldLeft(fa, B.empty) { (b, a) =>
B.combine(b, f(a))
}
foldLeft(fa, B.empty)((b, a) => B.combine(b, f(a)))

/**
* Traverse `F[A]` using `Applicative[G]`.
Expand All @@ -82,6 +86,7 @@ import simulacrum._
* `Applicative#map2`.
*
* For example:
*
* {{{
* def parseInt(s: String): Option[Int] = ...
* val F = Foldable[List]
Expand Down Expand Up @@ -132,17 +137,44 @@ import simulacrum._
def foldK[G[_], A](fga: F[G[A]])(implicit G: MonoidK[G]): G[A] =
fold(fga)(G.algebra)


/**
* find the first element matching the predicate, if one exists
* Find the first element matching the predicate, if one exists.
*/
def find[A](fa: F[A])(f: A => Boolean): Option[A] =
foldLazy[A,Option[A]](fa, Lazy.eager(None)){ a =>
if(f(a))
Fold.Return(Some(a))
else
Fold.Pass
foldRight(fa, Lazy.eager(None: Option[A])) { a =>
if (f(a)) Fold.Return(Some(a)) else Fold.Pass
}.value


/**
* Convert F[A] to a List[A].
*/
def toList[A](fa: F[A]): List[A] =
foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) =>
buf += a
}.toList


/**
* Convert F[A] to a List[A], only including elements which match `p`.
*/
def filter_[A](fa: F[A])(p: A => Boolean): List[A] =
foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) =>
if (p(a)) buf += a else buf
}.toList


/**
* Convert F[A] to a List[A], dropping all initial elements which
* match `p`.
*/
def dropWhile_[A](fa: F[A])(p: A => Boolean): List[A] =
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the reasoning/convention behind the underscores here and in filter_?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, I took the naming convention from other places in Foldable, where you have less powerful versions of sequence and traverse which are named sequence_ and traverse_ (as opposed to the methods named in Traversable).

Similarly, in this case we can't filter or dropWhile to an F[A] value, but only to a List[A] (unlike something like MonadFilter). So I decided to use the same convention.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, that makes sense. If I have this question, I imagine others will too. I wonder if there's a good way to document it and make it easy to discover.

foldLeft(fa, mutable.ListBuffer.empty[A]) { (buf, a) =>
if (buf.nonEmpty || p(a)) buf += a else buf
}.toList


/**
* Compose this `Foldable[F]` with a `Foldable[G]` to create
* a `Foldable[F[G]]` instance.
Expand All @@ -167,12 +199,6 @@ trait CompositeFoldable[F[_], G[_]] extends Foldable[λ[α => F[G[α]]]] {
def foldLeft[A, B](fga: F[G[A]], b: B)(f: (B, A) => B): B =
F.foldLeft(fga, b)((b, a) => G.foldLeft(a, b)(f))

/**
* Left assocative fold on F[G[A]] using 'f'
*/
override def foldRight[A, B](fga: F[G[A]], b: B)(f: (A, B) => B): B =
F.foldRight(fga, b)((a, b) => G.foldRight(a, b)(f))

/**
* Right associative lazy fold on `F` using the folding function 'f'.
*/
Expand Down
Loading