Either monad

Let’s talk monads again. And this time, I will show you the either monad. We’ll use it to model a process whose steps can fail; and we wish to abort the process whenever a step fails (and return the failures) and we wish to continue the process if the step succeeds.

The practical application is to use the either monad is code that prepares some HTTP requests, sends the requests and then parses the responses. In pseudo-code, we have:

prepare:        S => Either[Errors, Payload] 
request:  Payload => Either[Errors, Payload]
parse:    Payload => Either[Errors, R]

Where S is some starting type and R is the final return type. As you can probably tell, the entire operation is

op:   S => Either[Errors, R]

We now want to bind the three steps into the final op: the either monad will allow a value on the right to flow through the operation, while a value on the left will stop the process and immediately be returned. Still keeping with pseudo-code, we assemble the beast as

op(source: S) = prepare(source) >>= request >>= parse

The notion of a workflow helps; indeed, the operation op is best imagined as simple operation followed by decision:

Adding some type annotations will make the flow of values even clearer:

Right. Let’s turn on the real syntax and type up some almost complete Scala code:

import scalaz._
import Scalaz._

sealed trait Failure
case class ServiceUnavailable(message: String) extends Failure
case class TheSonsOfKhanAreRebelling() extends Failure
// and more to taste

case class Payload(contentType: String, body: String)

case class Input(...)
case class ParserSettings(...)

def work(input: Input, parserSettings: ParserSettings): 
  Either[Seq[Failure], Integer] = {

  def prepare(input: Input): Either[Seq[Failure], Payload] = {
    // do some work, ultimately hopefully
    Right(Payload("application/json", "..."))
  }

  def request(payload: Payload): Either[Seq[Failure], Payload] = {
    // prepare request
    val httpClient = new DefaultHttpClient()
    val post = new HttpPost("/location/to/post")

    // make the entity
    val entity = new StringEntity(payload.body)
    entity.setContentType(payload.contentType)
    post.setEntity(entity)

    // perform the post
    try {
      httpClient.execute(new HttpHost("localhost", 80, "http"), 
        post, new ResponseHandler[Either[Seq[Failure], Payload] {
        def handleResponse(response: HttpResponse) = {
          val entity = response.getEntity
          // process the input, ultimately returning
          Right(Payload("application/json", "...")
        }
      })
    } catch {
      case e: Exception => Left(ServiceUnavailable(e.getMessage) :: Nil)
    }
  }

  def parse(parserSettings: ParserSettings)
           (payload: Payload): Either[Seq[Failure], Integer] = {
    // use the parserSettings, but ultimately, parse and
    // somehow calculate, returning from the payload
    Right(42)
  }

  // We now have the three steps. Let's bind them together.
  prepare(input) >>= request >>= parse(parserSettings)
}

And so, using the either monad in Scalaz 6, we have bound together our functions into a workflow-like structure. The behaviour of the either monad (either monad because the return type of the steps is Either[_, _]) stops & returns whenever a step returns a value on the left and continues to the next step, passing the returned value on the right from the previous step to it.

The end result is nicely readable code and less work for you to propagate—in this particular case—failures.

This entry was posted in Jan's Blog and tagged , , . Bookmark the permalink.

14 Responses to Either monad

  1. Rob Dickens says:

    Jan,

    You’re implying that Either is being provided by Scalaz, but I’m pretty sure it ain’t. In fact, I’m not sure that Scalaz is being used at all in this snippet.

    Rob

  2. Jan Machacek says:

    Hi Rob,

    It is using Scalaz–what you’re seeing is implicit conversion of the functions prepare, request and parse into the monad and bound together with >>=. (If you don’t believe me, remove the import scalaz._; import Scalaz._ and see if it still compiles :) )

    Jan

  3. Rob Dickens says:

    Oops. Now I see.

    Sorry!

  4. Jan Machacek says:

    No worries–it’s just the >>= that is easily lost in the rest of the text, but that delivers the bang :)

    Jan

  5. Ivan Meredith says:

    Oh nice, normally if you use for comprehensions you need to use a right projection?

    Scalaz7 M2 has a right-biased Either to replace scala.Either btw :)

  6. Rob Dickens says:

    How about moving the imports to just before you use >>=?

    Otherwise, it’s hard work to understand where things are coming from.

    And it might even compile a bit faster!

  7. Jan Machacek says:

    That’s a good idea; and I’ll take faster compilation any time.

    Thanks!

    Jan

  8. Keith says:

    Jan,

    Thank you very much. Your posts are always very helpful.
    Every Friday morning, I looked forward to the “This Week in Scala” series. Have you guys decided to stop it, or is just a summer vacation thing?
    Thanks,

    Keith.

  9. Jan Machacek says:

    Hi Keith,

    Thanks for your comment–people were on holiday, but we’re back with the weekly scala post starting today!

    Jan

  10. Keith says:

    Gr8!! You cannot imagine how many times I click “refresh” on Friday mornings!

  11. Pingback: This week in #Scala (17/08/2012)? | Cake Solutions Team Blog

  12. Kostya Golikov says:

    @Rob Dickens
    Can you, please, say more how position of import statement affects compilation times?

  13. Rob Dickens says:

    Hi, I’m afraid I’m not qualified to say anything on this – my suggestion was based purely on intuition, rather than knowledge of how the Scala compiler works.

    Try the mailing lists – it would be good to find out.

  14. Jan Machacek says:

    Completely unscientific measurement (sample size 5, just one computer, Friday morning, …) comes out as (whole project compile times, without tests):

    import at the compilation unit level: 1:53.119s
    import at inside the function: 1:54.044s

    Intuitively, there should be some difference, but in a large project, it doesn’t make much difference because of all the other stuff that needs compiling.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>