Learn You a Haskell for Great Good says:
In this section, we’re going to explore a few functions that either operate on monadic values or return monadic values as their results (or both!). Such functions are usually referred to as monadic functions.
Unlike Haskell’s standard Monad
, Cats’ Monad
is more granularly designed with the hindsight of
weaker typeclasses.
Monad
FlatMap
and Applicative
Apply
Functor
Here there’s no question that all monads are applicative functors
as well as functors. This means we can use ap
or map
operator
on the datatypes that form a monad.
LYAHFGG:
It turns out that any nested monadic value can be flattened and that this is actually a property unique to monads. For this, the
join
function exists.
In Cats, the equivalent function called flatten
on FlatMap
.
Thanks to simulacrum, flatten
can also be injected as a method.
@typeclass trait FlatMap[F[_]] extends Apply[F] {
def flatMap[A, B](fa: F[A])(f: A => F[B]): F[B]
/**
* also commonly called join
*/
def flatten[A](ffa: F[F[A]]): F[A] =
flatMap(ffa)(fa => fa)
....
}
Since Option[A]
already implements flatten
we need to
make an abtract function to turn it into an abtract type.
import cats._, cats.syntax.all._
def join[F[_]: FlatMap, A](fa: F[F[A]]): F[A] =
fa.flatten
join(1.some.some)
// res0: Option[Int] = Some(value = 1)
If I’m going to make it into a function, I could’ve used the function syntax:
FlatMap[Option].flatten(1.some.some)
// res1: Option[Int] = Some(value = 1)
LYAHFGG:
The
filterM
function fromControl.Monad
does just what we want! … The predicate returns a monadic value whose result is aBool
.
Cats does not have filterM
, but there’s filterA
on TraverseFilter.
LYAHFGG:
The monadic counterpart to
foldl
isfoldM
.
I did not find foldM
in Cats, so implemented it myself, but it wasn’t stack-safe. Tomas Mikula added a better implementation and that got merged as #925:
/**
* Left associative monadic folding on `F`.
*/
def foldM[G[_], A, B](fa: F[A], z: B)(f: (B, A) => G[B])(implicit G: Monad[G]): G[B] =
foldLeft(fa, G.pure(z))((gb, a) => G.flatMap(gb)(f(_, a)))
Let’s try using this.
def binSmalls(acc: Int, x: Int): Option[Int] =
if (x > 9) none[Int] else (acc + x).some
(Foldable[List].foldM(List(2, 8, 3, 1), 0) {binSmalls})
// res2: Option[Int] = Some(value = 14)
(Foldable[List].foldM(List(2, 11, 3, 1), 0) {binSmalls})
// res3: Option[Int] = None
In the above, binSmalls
returns None
when it finds a number larger than 9.