简体   繁体   中英

Yet Another Haskell Rigid Type Variable Error

I've investigated many answers to other rigid type variable error questions; but, alas, none of them, to my knowledge, apply to my case. So I'll ask yet another question.

Here's the relevant code:

module MultipartMIMEParser where

import Control.Applicative ((<$>), (<*>), (<*))
import Text.ParserCombinators.Parsec hiding (Line)

data Header = Header { hName  :: String
                     , hValue :: String
                     , hAddl  :: [(String,String)] } deriving (Eq, Show)

data Content a = Content a | Posts [Post a] deriving (Eq, Show)

data Post a = Post { pHeaders :: [Header]
                   , pContent :: [Content a] } deriving (Eq, Show)

post :: Parser (Post a)
post = do
  hs <- headers
  c  <- case boundary hs of
          "" -> content >>= \s->return [s]
          b  -> newline >> (string b) >> newline >>
                manyTill content (string b)
  return $ Post { pHeaders=hs, pContent=c }

boundary hs = case lookup "boundary" $ concatMap hAddl hs of
                Just b  -> "--" ++ b
                Nothing -> ""
        -- TODO: lookup "boundary" needs to be case-insensitive.

content :: Parser (Content a)
content = do
  xs <- manyTill line blankField
  return $ Content $ unlines xs -- N.b. This is the line the error message refers to.
  where line = manyTill anyChar newline

headers :: Parser [Header]
headers = manyTill header blankField

blankField = newline

header :: Parser Header
header =
    Header <$> fieldName  <* string ":"
           <*> fieldValue <* optional (try newline)
           <*> nameValuePairs
  where fieldName = many $ noneOf ":"
        fieldValue = spaces >> many (noneOf "\r\n;")
        nameValuePairs = option [] $ many nameValuePair

nameValuePair :: Parser (String,String)
nameValuePair = do
  try $ do n <- name
           v <- value
           return $ (n,v)

name :: Parser String
name = string ";" >> spaces >> many (noneOf "=")

value :: Parser String
value = string "=" >> between quote quote (many (noneOf "\r\n;\""))
  where quote = string "\""

And the error message:

Couldn't match type `a' with `String'
  `a' is a rigid type variable bound by
      the type signature for content :: Parser (Content a)
      at MultipartMIMEParser.hs:(See comment in code.)
Expected type: Text.Parsec.Prim.ParsecT
                 String () Data.Functor.Identity.Identity (Content a)
  Actual type: Text.Parsec.Prim.ParsecT
                 String () Data.Functor.Identity.Identity (Content String)
Relevant bindings include
  content :: Parser (Content a)
    (bound at MultipartMIMEParser.hs:72:1)
In a stmt of a 'do' block: return $ Content $ unlines xs
In the expression:
  do { xs <- manyTill line blankField;
       return $ Content $ unlines xs }
In an equation for `content':
    content
      = do { xs <- manyTill line blankField;
             return $ Content $ unlines xs }
      where
          line = manyTill anyChar newline

From what I've seen, the problem is that I'm explicitly returning a String using unlines xs , and that breaks the generic nature of a in the type signature. Am I close to understanding?

I've declared Content to be generic because, presumably, this parser might eventually be used on types other than String . Perhaps I'm abstracting prematurely. I did try removing all my a s, but I started getting many more compile errors. I think I'd like to stick with the generic approach, if that's reasonable at this point.

Is it clear from the code what I'm trying to do? If so, any suggestions on how to do it best?

You're telling the compiler that content has type Parser (Content a) , but the line causing the error is

return $ Content $ unlines xs

Since unlines returns a String , and the Content constructor has type a -> Content a , here you would have String ~ a , so the value Content $ unlines xs has type Content String . If you change the type signature of content to Parser (Content String) then it should compile.


I've declared Content to be generic because, presumably, this parser might eventually be used on types other than String. Perhaps I'm abstracting prematurely. I did try removing all my as, but I started getting many more compile errors. I think I'd like to stick with the generic approach, if that's reasonable at this point.

It's fine to declare Content to be generic, and in many cases it is the exact right way to solve the problem, the issue is that while your container is generic, whenever you fill your container with something concrete, the type variables also have to be concrete. In particular:

> :t Container (1 :: Int)
Container 1 :: Container Int
> :t Container "test"
Container "test" :: Container String
> :t Container (Container "test")
Container (Container "test") :: Container (Container String)

Notice how all of these have their types inferred without any type variables left. You can use the container to hold whatever you want, you just have to make sure that you're accurately telling the compiler what it is.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM