Monad

Transformers

More than meets the eye

Monads

trait Monad[M[_]] {
  def flatMap[A, B](fa: M[A])(f: A => M[B]): M[B]
  def point[A](a: => A): M[A]
}
def dup[A](a: A) = List(a, a)

List(1, 2, 3) flatMap dup
// List(1, 1, 2, 2, 3, 3)
for {
  element <- List(1, 2, 3)
  dupped <- dup(element)
} yield dupped
// List(1, 1, 2, 2, 3, 3)
def findAge(name: String) =
  if (name == "Brian") Some(22)
  else None

Some("Brian") flatMap findAge
// Some(22)
Some("Jed") flatMap findAge
// None
None flatMap findAge
// None
for {
  name <- Some("Brian")
  age <- findAge(name)
} yield age
“Monads give us programmable semicolons”

Problem

 

Promise[Validation[A, B]]

(Validation shouldn't be a monad, TODO: use \/)

Short circuiting

Let's use Monad[Promise]

... And put validation logic everywhere

for {
  emailV <- getEmail(user)
  settingsV <- getSettings(user)
  checked <- emailV.fold(email =>
                Promise.pure _,
                settingsV.fold(settings =>
                  Promise.pure _,
                  checkDomains(user, email, settings)))
  authentication <- if (checked.isSuccess)
                       checkCredentials(user)
                     else
                       Promise.pure(checked)
} yield authentication

(for the node.js devs)

getEmail(user).flatMap(emailV =>
  getSettings(user).flatMap(settingsV =>
    emailV.fold(email =>
      Promise.pure _,
      settingsV.fold(settings =>
        Promise.pure _,
        checkDomains(user, email, settings).fold(
          Promise.pure _,
          _ => checkCredentials(user)))))

Let's use Monad[Validation]

... And block our threads for promises

for {
  email <- getEmail(user).value.get
  settings <- getSettings(user).value.get
  _ <- checkDomains(user, email, settings).value.get
  authentication <- checkCredentials(user).value.get
} yield authentication

Let's use Monad[Promise]

... And throw an exception to exit

for {
  emailV <- getEmail(user)
  settingsV <- getSettings(user)
  _ <- checkDomains(user, emailV, settingsV)
  authentication <- checkCredentials(user)
} yield authentication

You know what would be good?

A special Promise[Validation[A, B]] monad

Impossible

def flatMap[A, B](fa: M[N[A]])(f: A => M[N[B]]): M[N[B]]
“Monads don't compose”
Huh? What about Kleislis?

Kleisli >=>

(aka “compose, fishy, compose”)
(findAge >=> estimateExperience)("Brian")
Ah, monadic functions compose

... But not monads!

Impossible

def flatMap[A, B](fa: M[N[A]])(f: A => M[N[B]]): M[N[B]]
“Monads don't compose”

Solution

case class AsyncValidation[A](run: Promise[Validation[AuthFail, A]])
def point[A](a: => A): AsyncValidation[A]
AsyncValidation(a.point[Validation].point[Promise])
def flatMap[A, B]
           (fa: AsyncValidation[A])
           (f: A => AsyncValidation[B])
           : AsyncValidation[B]
AsyncValidation(fa.run.flatMap(v => v match {
  case Success(s) => f(s).run
  case Failure(_) => v.point[Promise]
}))
AsyncValidation(fa.run.flatMap(v => v match {
  case Success(s) => f(s).run
  case Failure(_) => v.point[Promise]
}))

Transformers

MonadTrans

ValidationT

(ValidationT shouldn't exist, TODO: use EitherT)
def flatMap[M[_], A, B]
           (fa: ValidationT[M, A, B])
           (f: A => ValidationT[M, A, B])
           : ValidationT[M, A, B]
ValidationT(fa.run.flatMap(v => v match {
  case Success(s) => f(s).run
  case Failure(_) => v.point[M]
}))
for {
  email <- getEmail(user)
  settings <- getSettings(user)
  _ <- checkDomains(user, email, settings)
  authentication <- checkCredentials(user)    
} yield authentication
for {
  email <- getEmail(user).liftM[AValidationT]
  settings <- getSettings(user)
  _ <- checkDomains(user, email, settings)
  authentication <- checkCredentials(user)
} yield authentication

Monad Stacks

type X = ReaderT[StateT[IO, XState], XConf]
Thanks!