Some useful monadic functions 

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
  • extends FlatMap and Applicative
  • extends Apply
  • extends 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.

flatten method 

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)

filterM method 

LYAHFGG:

The filterM function from Control.Monad does just what we want! … The predicate returns a monadic value whose result is a Bool.

Cats does not have filterM, but there’s filterA on TraverseFilter.

foldM function 

LYAHFGG:

The monadic counterpart to foldl is foldM.

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.