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
joinfunction 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
filterMfunction fromControl.Monaddoes 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
foldlisfoldM.
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.