Foldable type class instances can be defined for data structures that can be folded to a summary value.
In the case of a collection (such as List
or Set
), these methods will fold
together (combine) the values contained in the collection to produce a single
result. Most collection types have foldLeft
methods, which will usually be
used by the associated Foldable[_]
instance.
Foldable[F]
is implemented in terms of two basic methods:
foldLeft(fa, b)(f)
eagerly folds fa
from left-to-right.foldRight(fa, b)(f)
lazily folds fa
from right-to-left.These form the basis for many other operations, see also: A tutorial on the universality and expressiveness of fold
First some standard imports.
import cats._
import cats.implicits._
Apart from the familiar foldLeft
and foldRight
, Foldable
has a number of other useful functions.
foldLeft
is an eager left-associative fold on F
using the given function.
Foldable[List].foldLeft(List(1, 2, 3), 0)(_ + _) should be(res0)
Foldable[List].foldLeft(List("a", "b", "c"), "")(_ + _) should be(res1)
foldRight
is a lazy right-associative fold on F
using the given function.
The function has the signature (A, Eval[B]) => Eval[B]
to support laziness in
a stack-safe way.
val lazyResult =
Foldable[List].foldRight(List(1, 2, 3), Now(0))((x, rest) => Later(x + rest.value))
lazyResult.value should be(res0)
fold
, also called combineAll
, combines every value in the foldable using the given Monoid
instance.
Foldable[List].fold(List("a", "b", "c")) should be(res0)
Foldable[List].fold(List(1, 2, 3)) should be(
res1)
foldMap
is similar to fold
but maps every A
value into B
and then
combines them using the given Monoid[B]
instance.
Foldable[List].foldMap(List("a", "b", "c"))(_.length) should be(res0)
Foldable[List].foldMap(List(1, 2, 3))(_.toString) should be(res1)
foldK
is similar to fold
but combines every value in the foldable using the given MonoidK[G]
instance
instead of Monoid[G]
.
Foldable[List].foldK(List(List(1, 2), List(3, 4, 5))) should be(res0)
Foldable[List].foldK(List(None, Option("two"), Option("three"))) should be(res1)
find
searches for the first element matching the predicate, if one exists.
Foldable[List].find(List(1, 2, 3))(_ > 2) should be(res0)
Foldable[List].find(List(1, 2, 3))(_ > 5) should be(res1)
exists
checks whether at least one element satisfies the predicate.
Foldable[List].exists(List(1, 2, 3))(_ > 2) should be(res0)
Foldable[List].exists(List(1, 2, 3))(_ > 5) should be(res1)
forall
checks whether all elements satisfy the predicate.
Foldable[List].forall(List(1, 2, 3))(_ <= 3) should be(res0)
Foldable[List].forall(List(1, 2, 3))(_ < 3) should be(res1)
Convert F[A]
to List[A]
.
Foldable[List].toList(List(1, 2, 3)) should be(res0)
Foldable[Option].toList(Option(42)) should be(res1)
Foldable[Option].toList(None) should be(res2)
Convert F[A]
to List[A]
only including the elements that match a predicate.
Foldable[List].filter_(List(1, 2, 3))(_ < 3) should be(res0)
Foldable[Option].filter_(Option(42))(_ != 42) should be(res1)
traverse
the foldable mapping A
values to G[B]
, and combining
them using Applicative[G]
and discarding the results.
This method is primarily useful when G[_]
represents an action
or effect, and the specific B
aspect of G[B]
is not otherwise
needed. The B
will be discarded and Unit
returned instead.
import cats.implicits._
def parseInt(s: String): Option[Int] =
Either.catchOnly[NumberFormatException](s.toInt).toOption
Foldable[List].traverse_(List("1", "2", "3"))(parseInt) should be(res0)
Foldable[List].traverse_(List("a", "b", "c"))(parseInt) should be(res1)
We can compose Foldable[F[_]]
and Foldable[G[_]]
instances to obtain Foldable[F[G]]
.
val FoldableListOption = Foldable[List].compose[Option]
FoldableListOption.fold(List(Option(1), Option(2), Option(3), Option(4))) should be(res0)
FoldableListOption.fold(List(Option("1"), Option("2"), None, Option("3"))) should be(res1)
Hence when defining some new data structure, if we can define a foldLeft
and
foldRight
we are able to provide many other useful operations, if not always
the most efficient implementations, over the structure without further
implementation.
There are a few more methods that we haven't talked about but you probably can guess what they do:
Foldable[List].isEmpty(List(1, 2, 3)) should be(res0)
Foldable[List].dropWhile_(List(1, 2, 3))(_ < 2) should be(res1)
Foldable[List].takeWhile_(List(1, 2, 3))(_ < 2) should be(res2)