我在理解为什么推断类型签名与我期望的不同时遇到了一些困难。让我们举个例子(我尽量让它尽可能短):
import Control.Applicative
import Data.Word
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Token
import Text.Parsec.Language (emptyDef)
import Text.Parsec.Prim
import Data.Functor.Identity
--parseUInt' :: Num b => ParsecT String u Identity b
parseUInt' = fromInteger <$> decimal (makeTokenParser emptyDef)
--parseUInt1 = fromInteger <$> decimal (makeTokenParser emptyDef)
--parseUInt2 = fromInteger <$> decimal (makeTokenParser emptyDef)
parsePairOfInts = do
x <- parseUInt'
char ','
y <- parseUInt'
return $ (x, y)
parseLine :: String -> Either ParseError (Word32, Word8)
parseLine = parse parsePairOfInts "(error)"
main = print . show $ parseLine "1,2"
此代码无法编译:
test.hs:21:19:
Couldn't match type ‘Word32’ with ‘Word8’
Expected type: Parsec String () (Word32, Word8)
Actual type: ParsecT String () Identity (Word32, Word32)
In the first argument of ‘parse’, namely ‘parsePairOfInts’
In the expression: parse parsePairOfInts "(error)"
Failed, modules loaded: none.
但如果我取消注释parseUInt'
的类型签名,它就可以很好地编译。
同时,如果我在GHCi中查询类型信息,它看起来像这样:
λ>:t (fromInteger <$> decimal (makeTokenParser emptyDef))
(fromInteger <$> decimal (makeTokenParser emptyDef))
:: Num b => ParsecT String u Identity b
但如果我没有明确指定类型签名,那么&#39; b&#39;以某种方式将类型固定为Word32
。
如果我用两个不同的(但仍然是相同的实现)函数parseUInt'
和parseUInt1
替换parseUInt2
,代码也会编译。
我认为如果我没有指定函数的类型,推断类型必须是限制性最小的(Num b =>...
),但事实并非如此。
我在这里真正缺少什么?
答案 0 :(得分:8)
我认为这是可怕的MonomorphismRestriction
。如果您没有提供类型签名,那么ghc
会尝试推断具体类型签名,如果该函数被实例化为代码中其他地方的具体类型。 ghc
看到您使用该函数将Word32
解析为parsePairOfInt
的第一行,然后在达到{{1}的第二次使用之前将parseUInt'
修复为该类型两行下来。这会导致类型错误,因为类型已经实例化为parseUInt'
,现在类型需要为Word32
。
答案 1 :(得分:2)
再次看起来像monomorphism restriction。你定义了一些没有&#34;外观&#34;像一个多态值,所以编译器为它推断出一个单态类型。
事实证明这不是你想要的类型,所以你必须通过添加类型签名来明确你想要的多态性。