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 Parser
s 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.