Skip to content

Instantly share code, notes, and snippets.

@blouerat
Created April 27, 2015 14:53
Show Gist options
  • Save blouerat/973ac3fa01eb375460e4 to your computer and use it in GitHub Desktop.
Save blouerat/973ac3fa01eb375460e4 to your computer and use it in GitHub Desktop.
Monad, Applicative & Json

Say we have a morally correct json library.

It comes with types like: JValue, JObject and Path.

It also has error types extending JsonError like ParsingError, CastingError and FieldError. To make it even smarter, there's a sweet Semigroup[JsonError] to accumulate errors properly (left as an exercise).

Finally, there are a bunch of basic combinators:

def parse(in: String): ParsingError \/ JValue
def toJObject(jValue: JValue): CastingError \/ JObject
def toSeq(jValue: JValue): CastingError \/ Seq[JValue]
def field(jObject: JObject, path: Path): FieldError \/ JValue

We can now write:

def run(input: String): JsonError \/ (JObject, Seq[JValue]) =
  parse(input)
    .flatMap(toJObject)
    .flatMap { jObject => 
      val meta = field(jObject, "meta").flatMap(toJObject).validation
      val body = field(jObject, "body").flatMap(toSeq).validation
      meta.tuple(body).disjunction
    }

Or with a for comprehension:

def run(input: String): JsonError \/ (JObject, Seq[JValue]) =
  for {
    json <- parse(input)
    jObject <- toJObject(json)
    result <- {
      val meta = field(jObject, "meta").flatMap(toJObject).validation
      val body = field(jObject, "body").flatMap(toSeq).validation
      meta.tuple(body).disjunction
    }
  } yield result

magic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment