Search code examples
haskellmegaparsec

Why does this Megaparsec parser work in GHCi but does not compile in a source file?


I am a beginner user of Haskell and the Megaparsec library. While parsing a line of text, I come to a point where I need to parse the remaining text in the line up until the end-of-line (either LF or CRLF). My thought was to use some and noneOf but cannot get the code to compile even after testing in GHCi as follows:

λ> import Data.Text (Text, pack)
λ> import Data.Void
λ> import Text.Megaparsec as M
λ> import Text.Megaparsec.Char as M
λ> import qualified Text.Megaparsec.Char.Lexer as L
λ> type Parser = Parsec Void Text
λ> 
λ> parse (some (noneOf "\r\n")) "" (pack "a line of text\r\n")
Right "a line of text"
λ> parse (some (noneOf "\r\n")) "" (pack "a line of text\n")
Right "a line of text"

So the parser (some (noneOf "\r\n")) compiles successfully and returns what I expected: "a line of text" not including the end-of-line character(s). However, I cannot get the following code to compile in a source file

pLineValue :: Parser Text
pLineValue = do
    str <- (some (noneOf "\r\n"))
    return (pack str)

The compiler gives following error:

    • Ambiguous type variable ‘f0’ arising from a use of ‘noneOf’
      prevents the constraint ‘(Foldable f0)’ from being solved.
      Probable fix: use a type annotation to specify what ‘f0’ should be.
      These potential instances exist:
        instance Foldable (Either a) -- Defined in ‘Data.Foldable’
        instance Foldable Maybe -- Defined in ‘Data.Foldable’
        instance Foldable ((,) a) -- Defined in ‘Data.Foldable’
        ...plus one other
        ...plus 37 instances involving out-of-scope types
        (use -fprint-potential-instances to see them all)
    • In the first argument of ‘some’, namely ‘(noneOf "\r\n")’
      In a stmt of a 'do' block: str <- (some (noneOf "\r\n"))
      In the expression:
        do str <- (some (noneOf "\r\n"))
           return (pack str)
   |
78 |     str <- (some (noneOf "\r\n"))
   |                   ^^^^^^^^^^^^^

What am I doing wrong? What is the correct syntax in the source file? or is there a better approach to parse the remaining text up to but not including the LF or CRLF ending? I'd appreciate any help, Thanks.


Solution

  • noneOf takes an arbitrary Foldable container:

    noneOf :: (Foldable f, MonadParsec e s m) => f (Token s) -> m (Token s)
    

    "\r\n" is ordinarily a String, which is a list of Char:

    > :t "\r\n"
    "\r\n" :: [Char]
    
    > :i String
    type String = [Char]    -- Defined in ‘GHC.Base’
    

    However, if you have OverloadedStrings enabled, "\r\n" can be any IsString instance:

    > :set -XOverloadedStrings
    > :t "\r\n"
    "\r\n" :: IsString p => p
    

    Therefore the call to noneOf is ambiguous because the type of container isn’t pinned down:

    > :t noneOf "\r\n"
    noneOf "\r\n"
      :: (Foldable f, MonadParsec e s m,
          IsString (f (Token s))) =>
         m (Token s)
    

    The simple solution is to add a type annotation:

    > :t noneOf ("\r\n" :: [Char])
    noneOf ("\r\n" :: [Char])
      :: (MonadParsec e s m, Token s ~ Char) => m (Token s)
    

    You can observe this with any Foldable- or Traversable-polymorphic function like maximum or sum.

    Alternatively, you can use an explicit list instead:

    > :t noneOf ['\r', '\n']
    noneOf ['\r', '\n']
      :: (MonadParsec e s m, Token s ~ Char) => m (Token s)
    

    But be aware that this will have the same sort of underconstrained-type issue if you have OverloadedLists enabled:

    > :set -XOverloadedLists
    > :t noneOf ['\r', '\n']
    noneOf ['\r', '\n']
      :: (Foldable f, MonadParsec e s m,
          IsList (f (Token s)),
          Item (f (Token s)) ~ Char) =>
         m (Token s)
    

    If you encounter further strange differences between a source file and GHCi, it often comes down to differences that GHCi uses for convenience, such as the “extended default rules”, so trying :set -XNoExtendedDefaultRules vs. :set -XExtendedDefaultRules can sometimes help in situations like this.