Pure Functional Programming with Path Dependent Types (Parsers) in Scala?

806 views Asked by At

So when using Scala Parsers one may have:

case class MyParser(foos: List[String]) extends Parsers {
  val bars: List[Parser[String]] = foos.map(parserFromString)

  // Expensive function
  def parserFromString(s: String): Parser[String] = ???
}

Parser is a path-dependent type, so this code cannot be refactored so that bars can be passed in from the constructor. Note that parserFromString in my use case actually creates a MyParser in such a way that MyParser construction becomes O(N!) where N = foos.size.

So suppose now I wish to add to bars via another foo. The FP way would be to refactor to include bars in the constructor, then define something like def +(foo: String): MyParser = copy(bars = bars :+ parserFromString(foo), but as mentioned I can't I have to recreate from scratch.

My solution is just make bars a private var and mutate it with a Unit method update, i.e. def update(foo: String): Unit = bars +:= parserFromString(foo)

My first question is quite simple: am I stuck? Do I have to use mutation?

Second question: does Parboiled2 suffer from this? Do they use path-dependent types (at a glance it doesn't look like it), and if so why do Scala parsers use path-dependent types?

If parboiled2 does not suffer from path-dependent types this alone can be a reason to use it!

If anyone is wondering why I need to create Parsers from parameters it's because I'm implementing a language where users can define macros and use those macros in defining other macros. So no, before anyone tries telling me to change the design, I can't. I also really don't want mutability since I'm going to be needing multi-threading later.

1

There are 1 answers

2
Alexey Romanov On BEST ANSWER

Parser is a path-dependent type, so this code cannot be refactored so that bars can be passed in from the constructor.

Yes, it can:

// could also be trait and mixed in where you need it
object MyParsers extends Parsers {
  case class MyParser(bars: List[Parser[String]]) {
    def +(foo: String): MyParser = copy(bars = bars :+ parserFromString(foo))
    ...
  }
}

does Parboiled2 suffer from this? Do they use path-dependent types (at a glance it doesn't look like it)

No, as you can see from the examples.

and if so why do Scala parsers use path-dependent types

As shown above, this shouldn't be a problem. I have run into trouble with path-dependent types in some circumstances, but not in use of parsers.

For choice between Scala-parsers and parboiled, I'd personally think more about performance, which DSL you prefer, etc. Last time I've looked at parboiled, it had no real way to affect error reporting, but this is fixed now.