又一个Haskell刚性类型变量误差

时间:2014-11-17 14:50:34

标签: haskell

我已经研究了许多其他刚性类型变量错误问题的答案;但是,据我所知,他们都不适用于我的案子。所以我还会问另一个问题。

以下是相关代码:

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 "\""

错误信息:

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

从我所看到的情况来看,问题是我使用String明确返回unlines xs,这打破了a的一般性质类型签名。我接近理解吗?

我已声明Content是通用的,因为据推测,此解析器最终可能会用于String以外的其他类型。也许我过早地抽象了。我确实尝试删除了所有a,但我开始收到更多的编译错误。我认为我想坚持使用通用方法,如果这在当时是合理的。

从代码中可以清楚地知道我要做什么吗?如果是的话,有关如何做到最好的任何建议吗?

1 个答案:

答案 0 :(得分:4)

您告诉编译器content的类型为Parser (Content a),但导致错误的行是

return $ Content $ unlines xs

由于unlines返回String,而Content构造函数的类型为a -> Content a,因此您需要String ~ a,因此值为Content $ unlines xs有类型Content String。如果您将content的类型签名更改为Parser (Content String),那么它应该编译。


  

我已经宣布Content是通用的,因为据推测,这个解析器最终可能会用在String以外的类型上。也许我过早地抽象了。我确实尝试删除所有的as,但我开始收到更多的编译错误。我认为我想坚持使用通用方法,如果这在当时是合理的。

宣布Content是通用的,这很好,在许多情况下,这是解决问题的正确方法,问题是,当您的容器是通用的时,无论何时填充容器对于具体的东西,类型变量也必须是具体的。特别是:

> :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)

注意所有这些类型都是在没有任何类型变量的情况下推断出它们的类型。您可以使用容器来保存您想要的任何内容,您只需要确保准确地告诉编译器它是什么。