I have a list of values from which I can construct a list of parsers, that depend on these values by mapping (see example). Then what I want to do is turn the list of parsers into a single parser by concatenation.
One possibility is using foldLeft and ~:
parsers.foldLeft(success(Nil)){case (ps,p) => rs ~ p ^^ {case xs ~ x => x ::xs}} ^^ (_.reverse)
Is this efficient?
I don’t know how combinator parsers work; will there be a call stack with depth of length of the list? Thus may I run into SO errors for very long concatenations?
Better way
Is there a different way that is more readable?
Example
Suppose you have a file with two lines. The first line contains n integers x_1 to x_n. The second line contains contains x_1 + x_2 + … x_n integers that belong to groups according to the first line. I want to take the sequence of integers from the first line and create n parsers p_1 to p_n where p_i parses x_i integers.
Suppose I have the list of integers l = List(1,2,3) from the first line. For each integer n I create a parser that parses n integers: parsers = l.map(repN(_,integer)).
What you’re describing (and what you’ve more or less reinvented in your implementation with
foldLeftand~) is essentially Haskell’ssequencefor monads (really you only need an applicative functor, but that’s irrelevant here).sequencetakes a list of monadic values and returns a monadic list of values.Parseris a monad, sosequenceforParserwould change aList[Parser[A]]into aParser[List[A]].Scalaz gives you
sequence, but off the top of my head I don’t know if there’s a nice way to get the necessaryApplicativeinstance forParser. Fortunately you can roll your own pretty easily (I’m directly translating the Haskell definition):This gives us
List(List(1), List(2, 3), List(4, 5, 6))forparser("1 2 3 4 5 6"), as desired.(Note that I’m using
RegexParsershere as a convenient complete example, but the approach works more generally.)What’s going on might be a little clearer if we desugar the
forcomprehension:We can write
flatMapasintoandmapas^^:This isn’t too far from your formulation, except that we’re using a right fold instead of reversing and aren’t building up and breaking down the
~s.About efficiency: Both of our implementations are going to result in unpleasant call stacks. In my experience this is just a fact of life with Scala’s parser combinators. To quote another Stack Overflow answer, for example:
My
sequence-y approach addresses the “more readable” part of your question, and is almost certainly the cleanest way to solve the problem with Scala’s parser combinators. It’s marginally more efficient than your implementation, and should be fine for a few thousand groups or so. If you need to handle more than that, you’ll have to look outside ofscala.util.parsing.combinator. I’d recommend something like the following:No guarantees, but on my system it doesn’t overflow on a line with 100k integer groups.