Dealing with recursing types #152
Replies: 2 comments 12 replies
-
Good idea @randomeizer! I'm slightly concerned about the usability though. It "feels" a little odd. We have to acknowledge the recursivity, but the If I'd encounter the situation, I would personally probably try to extension ArithmeticExpression {
static var parser: AnyParser<Substring, ArithmeticExpression> {
Lazy {
OneOf {
Int.parser().map(ArithmeticExpression.value)
Parse(ArithmeticExpression.add) {
"("
Self.parser
"+"
Self.parser
")"
}
}
}.eraseToAnyParser()
}
} This is only an idea, I don't know if it works. If I would absolutely need to avoid erasure to But this is overall a very frequent use-case, so I'm definitely very interested to see where it goes! |
Beta Was this translation helpful? Give feedback.
-
It's worth pointing out that swift-parsing already contains an example arithmetic parser in its benchmarks suite, which is recursive but does not require explicit type erasure via Instead it implicitly defers laziness to a "value recursion" solution as you mention above. We believe that conforming your own types to parsers is probably the friendliest way to maintain a complex parser as it grows, especially when recursion is introduced. It's also worth noting that in the future we'd love to adopt a more SwiftUI-like interface for parsers. Something like the following: struct MyParser: Parser {
var body: some Parser<Substring, Bool> {
// @ParserBuilder syntax in here
}
} This would provide type-level recursion without the need for explicit erasure or laziness. We're still a Swift version or two away from being able to support this, but potentially soon enough! 😄 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
So, in my parsing adventures I have finally come across a recursing parser situation. In my case, it was with the children definition in the XML spec.
There are two challenges in getting recursive parsing to work:
AnyParser
, etc.Lazy
parser.Here's a simple example of a recursing type:
And here's a parser which works.
It has
Lazy
around the recursing part of the parser, and erases at the top level.Technically, only the recursing part needs to be type-erased though, so you could also do this:
You end up with a pretty gnarly type signature, but it only erases at the point where it is required to to allow correct recursion.
To make my own life simpler to remember all the steps required for recursion, I've created a
RecursivelyParse
parser, which both executes the wrapped parsers lazily, and erases the parsed types. It currently looks like this:Because it's type-erasing, I had to also create a related
RecursivelyParsePrint
, which implementsParsePrint
in a similar manner (is there any way to have a single type when type-erasing?)This is used like this:
It now correctly protects both the type signature and the lazy loading, hopefully reducing errors on my part down the track. I haven't implemented it yet, but plan to also support 'mapping' directly in the
RecursivelyParse
type, so you could do this:Which is a bit neater anyway. The
Int.parser()
is making up most of the noise in the signature at this point.Is it worth having something like
RecursivelyParse
in the standard library?Yes, you can homebrew it already using
Lazy
andAnyParser
, but it's not necessarily obvious, and you can forget to useLazy
and still have it compile correctly. This makes your intention explicit.Beta Was this translation helpful? Give feedback.
All reactions