Composable Parsing: Flat‑Map

Episode #60 • Jun 3, 2019 • Subscriber-Only

The map function on parsers is powerful, but there are still a lot of things it cannot do. We will see that in trying to solve some of its limitations we are naturally led to our old friend the flatMap function.

Previous episode
Composable Parsing: Flat‑Map
Next episode
Locked

Unlock This Episode

Our Free plan includes 1 subscriber-only episode of your choice, plus weekly updates from our newsletter.

Sign in with GitHub

Introduction

Hopefully this is tickling something in the back of your mind because it’s something we devoted 5 entire Point-Free episodes to. We really hammered on the idea of having generic types that are capable of chaining their computations together. We found out that the natural solution to this “chaining” or “sequencing” was none other than flatMap, which is defined on arrays and optionals in the standard library, but the idea goes far, far beyond just what Swift gives us. So, let’s see what it would look like for our Parser type.

Flat‑map on Parser

Recall that the general shape of flatMap looks something like this:

flatMap: ((A) -> M<B>) -> (M<A>) -> M<B>

This says that if you have a function that transforms values into the generic container, then you can lift it to a function between generic containers. This signature is the essence of what it means to chain computations together. For example, when dealing with optionals it allows you to chain together multiple transformations that may fail by returning nil. Or if dealing with asynchronous values it allows you to chain together multiple computations one after the other.

Let’s try to implement this function for our Parser type. Let’s first just get the signature in place:

extension Parser {
  func flatMap<B>(_ f: (A) -> Parser<B>) -> Parser<B> {

  }
}

We know we need to return a Parser<B>, so let’s invoke its initializer, which takes a block representing its run function (inout Substring) -> B?.

extension Parser {
  func flatMap<B>(_ f: (A) -> Parser<B>) -> Parser<B> {
    return Parser<B> { str -> B? in

    }
  }
}

What can we do with the pieces that we have at our disposal right now. Well, one simple thing would be to start by running our parser on the input string to get some matching value in A:

extension Parser {
  func flatMap<B>(_ f: (A) -> Parser<B>) -> Parser<B> {
    return Parser<B> { str in
      let matchA = self.run(&str)
    }
  }
}

Now that we have a value in A, though really it’s an optional value, we can try plugging it into our function f which takes values in A. That will give us a parser of things in B:

extension Parser {
  func flatMap<B>(_ f: (A) -> Parser<B>) -> Parser<B> {
    return Parser<B> { str in
      let matchA = self.run(&str)
      let parserB = matchA.map(f)
    }
  }
}

Now, what can we do with a parser of B values? Well, let’s try running it on our input string again, which will allow us to consume even more of the string:

extension Parser {
  func flatMap<B>(_ f: (A) -> Parser<B>) -> Parser<B> {
    return Parser<B> { str in
      let matchA = self.run(&str)
      let parserB = matchA.map(f)
      let matchB = parserB?.run(&str)
    }
  }
}

And because our transform function f is captured by the parser’s run function, we must mark it @escaping since it escapes the lifetime of a call to flatMap.

extension Parser {
  func flatMap<B>(_ f: @escaping (A) -> Parser<B>) -> Parser<B> {
    return Parser<B> { str in
      let matchA = self.run(&str)
      let parserB = matchA.map(f)
      let matchB = parserB?.run(&str)
    }
  }
}

And we now have an optional value in B, which is exactly what we need to return from our parser, so maybe we’re done!

extension Parser {
  func flatMap<B>(_ f: @escaping (A) -> Parser<B>) -> Parser<B> {
    return Parser<B> { str in
      let matchA = self.run(&str)
      let parserB = matchA.map(f)
      let matchB = parserB?.run(&str)
      return matchB
    }
  }
}

Flat‑map backtracking


References

  • Combinators
    Daniel Steinberg • Sep 14, 2018

    Daniel gives a wonderful overview of how the idea of “combinators” infiltrates many common programming tasks.Just as with OO, one of the keys to a functional style of programming is to write very small bits of functionality that can be combined to create powerful results. The glue that combines the small bits are called Combinators. In this talk we’ll motivate the topic with a look at Swift Sets before moving on to infinite sets, random number generators, parser combinators, and Peter Henderson’s Picture Language. Combinators allow you to provide APIs that are friendly to non-functional programmers.

  • Parser Combinators in Swift
    Yasuhiro Inami • May 2, 2016

    In the first ever try! Swift conference, Yasuhiro Inami gives a broad overview of parsers and parser combinators, and shows how they can accomplish very complex parsing.Parser combinators are one of the most awesome functional techniques for parsing strings into trees, like constructing JSON. In this talk from try! Swift, Yasuhiro Inami describes how they work by combining small parsers together to form more complex and practical ones.

  • Learning Parser Combinators With Rust
    Bodil Stokke • Apr 18, 2019

    A wonderful article that explains parser combinators from start to finish. The article assumes you are already familiar with Rust, but it is possible to look past the syntax and see that there are many shapes in the code that are similar to what we have covered in our episodes on parsers.

  • Sparse
    John Patrick Morgan • Jan 12, 2017

    A parser library built in Swift that uses many of the concepts we cover in our series of episodes on parsers.Sparse is a simple parser-combinator library written in Swift.

  • parsec
    Daan Leijen, Paolo Martini, Antoine Latter

    Parsec is one of the first and most widely used parsing libraries, built in Haskell. It’s built on many of the same ideas we have covered in our series of episodes on parsers, but using some of Haskell’s most powerful type-level features.

  • Parse, don’t validate
    Alexis King • Nov 5, 2019

    This article demonstrates that parsing can be a great alternative to validating. When validating you often check for certain requirements of your values, but don’t have any record of that check in your types. Whereas parsing allows you to upgrade the types to something more restrictive so that you cannot misuse the value later on.

  • Ledger Mac App: Parsing Techniques
    Chris Eidhof & Florian Kugler • Aug 26, 2016

    In this free episode of Swift talk, Chris and Florian discuss various techniques for parsing strings as a means to process a ledger file. It contains a good overview of various parsing techniques, including parser grammars.

Downloads

Get started with our free plan

Our free plan includes 1 subscriber-only episode of your choice, access to 62 free episodes with transcripts and code samples, and weekly updates from our newsletter.

View plans and pricing