Skip to content

Combinator variations #177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jamesdbrock opened this issue Apr 3, 2022 · 12 comments · Fixed by #182
Closed

Combinator variations #177

jamesdbrock opened this issue Apr 3, 2022 · 12 comments · Fixed by #182

Comments

@jamesdbrock
Copy link
Member

We need to figure out which combinators to keep and which are redundant after the CPS refactor #154

@jamesdbrock
Copy link
Member Author

Here is what the benchmarks from #176 (comment) show.

  • List.manyRec is faster than List.many. Let's export a Combinators.many = List.manyRec.
  • sepByRec is faster than sepBy. sepBy is not stack-safe. Let's replace sepBy with sepByRec.
  • chainlRec is faster than chainl. chainl is not stack-safe. Let's replace chainl with chainlRec.
  • chainr is faster than chainrRec. chainr is not stack-safe. Not clear what to do here.
  • manyTillRec is faster than manyTill. manyTill is not stack-safe. Let's replace manyTill with manyTillRec.

@jamesdbrock
Copy link
Member Author

Also, why are some of the combinators still blowing the stack?

@natefaubion
Copy link
Contributor

natefaubion commented Apr 3, 2022

Ideally the non Rec versions should still be stack safe. I don’t think we should replace them without understanding why they blow the stack.

@jamesdbrock
Copy link
Member Author

Ok.

@natefaubion
Copy link
Contributor

natefaubion commented Apr 3, 2022

I can't replicate the stack issues with sepBy. When I run this program:

module Main where

import Prelude

import Data.Array as Array
import Data.List (List)
import Data.String (joinWith)
import Effect (Effect)
import Effect.Class.Console (logShow)
import Parsing (Parser, runParser)
import Parsing.Combinators (sepBy)
import Parsing.String (string)

testString :: String
testString = joinWith " " $ Array.replicate 100000 ("A")

testParser :: Parser String (List String)
testParser = sepBy (string "A") (string " ")

main :: Effect Unit
main = do
  let res = runParser testString testParser
  logShow res

I get the full result printed. My guess would be that there is a stack issue elsewhere, not necessarily in the actual execution of the parser.

@natefaubion
Copy link
Contributor

Oh, interesting! I think it's because the parser actually fails to parse. You have "23" repeated, and are using sepBy, which will result in a parse error. Your original example is blowing the stack in the throw branch of alt. We need to bounce the throw continuation.

@jamesdbrock
Copy link
Member Author

@natefaubion fixed the stack safety of alt and bind in #178

@jamesdbrock
Copy link
Member Author

jamesdbrock commented Apr 5, 2022

I fixed the parse errors that you noticed, here are the new benchmarks: #179 (comment)

  • List.manyRec is faster than List.many. Let's export a Combinators.many = List.manyRec.
  • sepByRec and sepBy appear to be about the same speed. Let's delete sepByRec?
  • chainlRec is faster than chainl. Let's replace chainl with chainlRec?
  • chainr is faster than chainrRec. Let's delete chainrRec?
  • manyTillRec is faster than manyTill. Let's replace manyTill with manyTillRec?

Or maybe we should try to do #155 first before we make these decisions.

@jamesdbrock
Copy link
Member Author

I fixed the parse errors that you noticed, here are the new benchmarks: #180 (comment)

  • List.manyRec is faster than List.many. Let's export a Combinators.many = List.manyRec.
  • sepByRec is faster then sepBy. Let's replace sepBy with sepByRec?
  • chainlRec is faster than chainl. Let's replace chainl with chainlRec?
  • chainr is faster than chainrRec. Let's delete chainrRec?
  • manyTillRec is faster than manyTill. Let's replace manyTill with manyTillRec?

Or maybe we should try to do #155 first before we make these decisions.

@jamesdbrock
Copy link
Member Author

I think what I'm seeing here #181 (comment)
is that

  • The way to write the combinators so that they will run fastest is by writing the combinators in terms of tailRecM, which is how sepEndBy1Rec is written.
  • The second fastest way is to write them in terms of List.manyRec, which is how sepByRec is written. (List.manyRec is written in terms of tailRecM).
  • It's possible that we could squeeze even more speed out by writing these combinators CPS-style as I think @natefaubion suggested in audit and improve combinator implementations #155 ? But if we did that then we would still be using tailRecM, I think?

@jamesdbrock
Copy link
Member Author

I still don't understand why chairr is faster than chainrRec #181 (comment)

@natefaubion
Copy link
Contributor

natefaubion commented Apr 6, 2022

<|> pure (Done $ foldl apply last init)

chainrRec is probably slower because the foldl is being strictly computed on every iteration, making it quadratic. It needs to be defered, or guarded in some way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging a pull request may close this issue.

2 participants