我已经研究了许多其他刚性类型变量错误问题的答案;但是,据我所知,他们都不适用于我的案子。所以我还会问另一个问题。
以下是相关代码:
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
,但我开始收到更多的编译错误。我认为我想坚持使用通用方法,如果这在当时是合理的。
从代码中可以清楚地知道我要做什么吗?如果是的话,有关如何做到最好的任何建议吗?
答案 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)
注意所有这些类型都是在没有任何类型变量的情况下推断出它们的类型。您可以使用容器来保存您想要的任何内容,您只需要确保准确地告诉编译器它是什么。