Composing monadic functions 

LYAHFGG:

When we were learning about the monad laws, we said that the <=< function is just like composition, only instead of working for normal functions like a -> b, it works for monadic functions like a -> m b.

In Cats there’s a special wrapper for a function of type A => F[B] called Kleisli:

/**
 * Represents a function `A => F[B]`.
 */
final case class Kleisli[F[_], A, B](run: A => F[B]) { self =>

  ....
}

object Kleisli extends KleisliInstances with KleisliFunctions

private[data] sealed trait KleisliFunctions {

  def pure[F[_], A, B](x: B)(implicit F: Applicative[F]): Kleisli[F, A, B] =
    Kleisli(_ => F.pure(x))

  def ask[F[_], A](implicit F: Applicative[F]): Kleisli[F, A, A] =
    Kleisli(F.pure)

  def local[M[_], A, R](f: R => R)(fa: Kleisli[M, R, A]): Kleisli[M, R, A] =
    Kleisli(f andThen fa.run)
}

We can use the Kleisli() constructor to construct a Kliesli value:

scala> import cats._, cats.data._, cats.implicits._
import cats._
import cats.data._
import cats.implicits._

scala> val f = Kleisli { (x: Int) => (x + 1).some }
f: cats.data.Kleisli[Option,Int,Int] = Kleisli(<function1>)

scala> val g = Kleisli { (x: Int) => (x * 100).some }
g: cats.data.Kleisli[Option,Int,Int] = Kleisli(<function1>)

We can then compose the functions using compose, which runs the right-hand side first:

scala> 4.some >>= (f compose g).run
res0: Option[Int] = Some(401)

There’s also andThen, which runs the left-hand side first:

scala> 4.some >>= (f andThen g).run
res1: Option[Int] = Some(500)

Both compose and andThen work like function composition but note that they retain the monadic context.

lift method 

Kleisli also has some interesting methods like lift, which allows you to lift a monadic function into another applicative functor. When I tried using it, I realized it’s broken, so here’s the fixed version #354:

  def lift[G[_]](implicit G: Applicative[G]): Kleisli[λ[α => G[F[α]]], A, B] =
    Kleisli[λ[α => G[F[α]]], A, B](a => Applicative[G].pure(run(a)))

Here’s how we can use it:

scala> val l = f.lift[List]
l: cats.data.Kleisli[[α]List[Option[α]],Int,Int] = Kleisli(<function1>)

scala> List(1, 2, 3) >>= l.run
res2: List[Option[Int]] = List(Some(2), Some(3), Some(4))