pointfreeco / episode-code-samples

💾 Point-Free episode code.
https://www.pointfree.co
MIT License
973 stars 294 forks source link

Composable parser: zip #43

Closed simme closed 4 years ago

simme commented 4 years ago

Hello,

Quick question: why does a failure of Parser<A> not result in a restoration of the input string? https://github.com/pointfreeco/episode-code-samples/blob/master/0061-composable-parsing-zip/Composable%20Parsers.playground/Contents.swift#L202

Best, Simon

mbrandonw commented 4 years ago

Great question! It would probably be most correct to restore str from the original if the first guard failed:

func zip<A, B>(_ a: Parser<A>, _ b: Parser<B>) -> Parser<(A, B)> {
  return Parser<(A, B)> { str -> (A, B)? in
    let original = str
    guard let matchA = a.run(&str) else {
      str = original
      return nil
    }
    guard let matchB = b.run(&str) else {
      str = original
      return nil
    }
    return (matchA, matchB)
  }
}

However, in practice, if a parser fails it (ideally) shouldn't consume any of the input. So if the first parser fails, and nothing in consumed, we don't really need to do any of the str = original dance. We only need to do it in the second guard because then the first parser would have consumed some of the input.

It's a little tricky, and we haven't explicitly made the requirement that parsers should never consume input if they fail. If this were turned into an open source library, such requirements should be made in a very obvious way.

But for now it's probably safest to just do the str = original dance in the first guard.

Thanks!

simme commented 4 years ago

Right, that makes sense. In a nutshell: there's an unspoken rule that parsers should not consume any of the input if it fails. But since that's hard to enforce it's better to be safe than sorry.

Thanks for a great resource!