[英]How to understand Haskell compiler messages
美好的一天。
这是简单的“猜数”片段,它包含单个错误,但编译器使得很难解决错误:
import System.Random
import Control.Monad
import Control.Monad.Cont
main = do
rn <- randomRIO (1,10) :: IO Int
runContT (callCC $ \k -> forever $ do
lift $ putStr "Your number:"
n <- lift (fmap read getLine)
when (n == rn) $ k
lift $ putStrLn $ if (n > rn)
then "Too much"
else "Too little") (return)
putStrLn $ "Good guess! " ++ (show rn)
GHC给出错误:
> simple.hs:11:21:
> Couldn't match expected type `()' against inferred type `m b'
> Expected type: a -> ()
> Inferred type: a -> m b
> In the second argument of `($)', namely `k'
> In a stmt of a 'do' expression: when (n == rn) $ k
它让我真的很困惑,它告诉某些预期类型'()',但如何发现谁是这个“东西”? 是“k”吗? 似乎并非如此。 如果我们交换预期和推断它看起来更健康,但它现在看起来如何,它是非常令人困惑的。 我的问题是:如何发现原因并修复此错误?
要了解这些类型,最好查看周围的函数。
该错误提到了变量k
,它首先出现在表达式callCC $ \\k -> forever ...
我们可以通过查看callCC
的类型来获取k的类型:
callCC :: MonadCont m => ((a -> m b) -> m a) -> m a
由此,我们可以看到k
的类型为a -> mb
。 请注意,由于b
未在该函数中的任何其他位置使用,因此它的类型无关紧要,并且将由使用该函数的上下文确定。
k
在$
表达式之后用于$
(实际上并不需要)。 何时的类型是:
when :: Monad m => Bool -> m () -> m ()
注意第二个参数需要一个m ()
,但是你传入k,它有a -> mb
类型a -> mb
(因为b
无所谓它可以匹配()
。所以很明显需要给k
一些参数。不管是什么,我们回顾一下callCC的定义。那个arg就是你的程序中forever $ do ...
的价值。
看着永远的类型:
forever :: Monad m => m a -> m b
它需要一个monadic计算ma
,结果返回另一个monadic计算mb
。 注意b
不会出现在forever
的参数中。 这意味着类型由调用它的上下文决定(如read "3"
可以是Double
或Int
类型,具体取决于它所在的表达式)。 这由runContT
决定:
runContT :: ContT r m a -> (a -> m r) -> m r
如果您匹配从类型变量runContT
, callCC
和forever
,你会注意到,在b
中forever
对应于a
在runContT
。 a
在runContT
的第二个参数中runContT
,在程序中return
。 return
的类型为a -> ma
,这样的类型, a
是相同的r
在你的程序。 r
出现在输出mr
。
runContT
表达式位于do上下文中,没有任何绑定( <-
)。 所以你的代码等同于:
main = do
rn <- randomRIO (1,10) :: IO Int
runContT (callCC ....) (return) >> (putStrLn $ "Good guess! " ++ (show rn))
通过查看>>
的类型,最终可以解决这个谜团:
(>>) :: Monad m => m a -> m b -> m b
>>
丢弃传递给它的第一个monadic计算的值(这是runContT
表达式)。 这样计算回报率实际上并没有问题的值(注意如何a
没有出现在结果>>
功能)。 如果你通过这个解释来回顾这个结果,你会发现传递给k
的变量实际上并不重要! 如果你传递任何东西,该函数将正常工作:
import System.Random
import Control.Monad
import Control.Monad.Cont
main = do
rn <- randomRIO (1,10) :: IO Int
runContT (callCC $ \k -> forever $ do
lift $ putStr "Your number:"
n <- lift (fmap read getLine)
when (n == rn) $ k (Just ("Seriously anything works here", 42, [42..]))
lift $ putStrLn $ if (n > rn)
then "Too much"
else "Too little") (return)
putStrLn $ "Good guess! " ++ (show rn)
所以这是一个非常难的例子,我理解为什么你没有遵循它。 尽管如此,你确实会有更好的经验。 此外,延续monad是非常先进和复杂的haskell。
GHC警告消息中要关注的关键事项是:
你有类型; 它需要的类型
Couldn't match expected type `()' against inferred type `m b'
所以你有一个类型()
东西,在monadic设置mb
。
什么表达是错的
In the second argument of `($)', namely `k'
所以k
的类型错误。
行号
simple.hs:11:21
在第11行。
不要过于依赖“预期”与“推断”。 哪个并不总是显而易见且相关性较低; 重要的是类型检查器有关于某个术语类型的冲突信息。 最常见的情况是上下文期望一种类型(例如,应用采用特定类型的参数的函数),而推断该术语的不同类型。
现在,对于你得到的错误:
Couldn't match expected type `()' against inferred type `m b'
这意味着冲突类型是()
和mb
。 请注意,这些不一定是任何实际表达式的完整类型; 只是那个有冲突的部分。
Expected type: a -> ()
Inferred type: a -> m b
这里我们有两种实际类型。 a ->
部分不冲突,所以上面没有提到。
In the second argument of `($)', namely `k'
这告诉我们发现冲突的上下文,并给出其类型有争议的表达式,即k
。
这里的推断类型是“已经知道”为类型k
。 它来自哪里? k
被绑定为传递给callCC
的lambda的参数,该lambda具有类型((a -> mb) -> ma) -> ma
,因此这是推断类型。
这里的期望类型是when
的第二个参数,它的类型为Bool -> m () -> m ()
,它给出了m ()
。 a -> ()
来自哪里? 我们得到它,因为a -> _
等同于(->) a
,它在类型签名when
与类型变量m
统一。
显然,这不是你想要的类型,但你问如何解释错误,所以我会留下它。
预期类型表示您应该在该位置拥有的内容。 在你的情况下,我们有when :: (Monad m) => Bool -> m () -> m ()
。 因此,编译器推断,该k
中when (n == rn) $ k
应该是类型的a -> ()
推断类型是指编译器为变量推导出的实际类型。 在你的情况下,我们有callCC :: (MonadCont m) => ((a -> mb) -> ma) -> ma
。 这意味着它接收的匿名函数类型为(a -> mb) -> ma
。 由于k
是此函数的第一个参数,因此编译器断定k
似乎是 a -> mb
类型。
由于这两种类型不匹配,您会收到错误。
这种情况下的错误是你不给延续k
提供从callCC
返回的值。
更改
when (n == rn) $ k
至
when (n == rn) $ k ()
诀窍。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.