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
notFollowedBy is a no-op for attoparsec and yoctoparsec, say #85
Comments
An easy way to check your reasoning is simply to perform substitution and see what happens on a test case. In this case, if I, at GHCi...
We can therefore see that the step you are labeling "distributing the alternative" changes behavior (as does the replacement of |
Hi @nwf, thanks for this great response! I've followed your steps on my end as well, and I understand that there is no law that would justify my "distribution" rewrite. To the contrary, this substitution is in general wrong, as illustrated by your example. Still, I'm a bit puzzled, because I've been also working with yoctoparsec, https://hackage.haskell.org/package/yoctoparsec, for which the above definition of Please consider: λ> import Control.Applicative
λ> import Control.Monad (replicateM)
λ> import Control.Monad.Yoctoparsec
λ> notFollowedBy p = optional p >>= maybe (pure ()) (const empty)
λ> char x = mfilter (== x) token
λ> parseString @[] (replicateM 2 token <* notFollowedBy (char 'b')) "aab"
[("aa","b")] Here I'm using the λ> import Control.Applicative
λ> import Control.Monad (replicateM)
λ> import Data.Attoparsec.Text
λ> notFollowedBy p = optional p >>= maybe (pure ()) (const empty)
λ> parse (replicateM 2 anyChar <* notFollowedBy (char 'b')) "aab"
λ> Fail "" [] "Failed reading: empty" It seems that, in yoctoparsec's case, the above definition of |
Ah ha. Apparently this ultimately, as noted by https://wiki.haskell.org/MonadPlus, comes down to "The precise set of rules that MonadPlus should obey is not agreed upon.", a truly unfortunate situation. Therein, you will see that Given this state of affairs, it's not immediately clear to me that a general |
Hi @nwf, yes! That's exactly what's going on! Thanks for pointing this out. I went back to the basics of parsing, backtracking, and logic programming, and I think I've found the right solution for yoctoparsec: The
In order to use this combinator with yoctoparsec, I needed to define an instance for instance (Functor f, MonadLogic b) => MonadLogic (FreeT f b) where
-- msplit :: FreeT f b a -> FreeT f b (Maybe (a, FreeT f b a))
msplit (FreeT b) = FreeT $ do
r <- msplit b
case r of
Nothing -> pure . Pure $ Nothing
Just (val, b') ->
case val of
Pure a -> pure . Pure $ Just (a, FreeT b')
Free w -> pure . Free $ fmap msplit w With this, I can now do: λ> import Control.Applicative
λ> import Control.Monad (replicateM)
λ> import Control.Monad.Yoctoparsec
λ> import Control.Monad.Logic
λ> notFollowedBy p = lnot p
λ> char x = mfilter (== x) token
λ> parseString @[] (replicateM 2 token <* notFollowedBy (char 'b')) "aab"
[]
λ> parseString @[] (replicateM 2 token <* notFollowedBy (char 'b')) "aac"
[("aa","")] I'm quite happy with this. What do you think? |
Update: The above implementation of instance (Applicative f, MonadLogic b) => MonadLogic (FreeT f b) where
-- msplit :: FreeT f b a -> FreeT f b (Maybe (a, FreeT f b a))
msplit (FreeT b) = FreeT $ go b []
where
go b ws = do
r <- msplit b
case r of
Nothing -> pure $ case ws of
[] -> Pure Nothing
(w : ws) ->
let go' fas [] = fas
go' fas (w : ws) = go' (liftA2 (:) w fas) ws
in Free $ fmap (msplit . asum) (go' (fmap pure w) ws)
Just (val, b') ->
case val of
Pure a -> pure . Pure $ Just (a, FreeT b')
Free w -> go b' (w : ws) With this: λ> parseString @[] (lnot $ char 'b' <|> char 'c') "aca"
[((),"ca")]
λ> parseString @[] (lnot $ char 'b' <|> char 'c') "bca"
[]
λ> parseString @[] (lnot $ char 'b' <|> char 'c') "cca"
[]
λ> parseString @[] (lnot $ char 'b' <|> char 'c') "dca"
[((),"ca")] Edit: I just realize that |
Hi,
I've been studying this definition of the
notFollowedBy
combinator for attoparsec:parsers/src/Text/Parser/Combinators.hs
Line 456 in 9b86500
I am now fairly convinced that this is a no-op, but I'd like to ask for some feedback for my reasoning from someone who knows the subject matter better than myself. To this end, please consider the following reduction steps:
which does nothing when used like this:
p <* pure ()
.pure ()
always succeeds and consumes nothing. so this is equivalent top
.Can someone please comment on this reasoning?
Thanks :)
The text was updated successfully, but these errors were encountered: