美好的一天。
这是简单的“猜数”片段,它包含单个错误,但编译器 这让我很难理解错误:
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”吗?似乎并非如此。 如果我们交换预期和推断它看起来更健康,但它现在看起来如何,它是非常令人困惑的。我的问题是:如何发现原因并修复此错误?
答案 0 :(得分:10)
要了解这些类型,最好查看周围的功能。
错误提到变量k
,它首先出现在表达式callCC $ \k -> forever ...
中。我们可以通过查看callCC
的类型来获取k的类型:
callCC :: MonadCont m => ((a -> m b) -> m a) -> m a
从那以后,我们可以看到k
的类型为a -> m b
。请注意,由于b
未在该函数的任何其他位置使用,因此它的类型无关紧要,并且将由使用该函数的上下文确定。
k
(实际上并不需要)之后的when表达式中使用了 $
。何时的类型是:
when :: Monad m => Bool -> m () -> m ()
注意第二个参数需要一个m ()
,但是你传入k,其类型为a -> m b
(因为b
并不重要,它可以匹配()
。所以显然需要给k
一些论点。为了弄清楚是什么,我们回顾一下callCC的定义。那个arg是你程序中的值forever $ do ...
。
看着永远的类型:
forever :: Monad m => m a -> m b
它需要一个monadic计算m a
,结果返回另一个monadic计算m b
。请注意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
中的1}}。 runContT
用于return
的第二个参数,在您的程序中为return
。 a -> m a
的类型为a
,因此r
的类型与程序中的r
相同。输出m r
中会显示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
:
>>
runContT
会丢弃传递给它的第一个monadic计算的值(即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)
的变量实际上并不重要!如果您传递任何内容,该函数将正常工作:
{{1}}
所以这是一个非常难的例子,我明白为什么你没有遵循它。尽管如此,你确实会有更好的经验。此外,延续monad是非常先进和复杂的haskell。
答案 1 :(得分:9)
GHC警告信息中要注意的关键事项是:
你有类型;类型需要
Couldn't match expected type `()' against inferred type `m b'
所以你有一个()
类型的东西,在monadic环境中m b
。
哪个表达式有错误
In the second argument of `($)', namely `k'
所以k
的类型错误。
行号
simple.hs:11:21
第11行。
答案 2 :(得分:5)
不要过于依赖“预期”与“推断”。哪个并不总是显而易见且相关性较低;重要的是类型检查器有关于某个术语类型的冲突信息。最常见的情况是上下文需要一种类型(例如,应用一个接受特定类型参数的函数),而推断该术语使用不同的类型。
现在,对于你得到的错误:
Couldn't match expected type `()' against inferred type `m b'
这意味着冲突类型为()
和m b
。请注意,这些不一定是任何实际表达式的完整类型;只是那个有冲突的部分。
Expected type: a -> ()
Inferred type: a -> m b
这里有两种实际类型。 a ->
部分没有冲突,因此上面没有提到。
In the second argument of `($)', namely `k'
这告诉我们发现冲突的上下文,并给出其类型有争议的表达式,即k
。
此处的推断类型是{“1}}已”知道“的类型。它来自哪里? k
被绑定为传递给k
的lambda的参数,该lambda具有类型callCC
,因此这是推断类型。
此处的预期类型是((a -> m b) -> m a) -> m a
的第二个参数,其类型为when
,它为我们提供Bool -> m () -> m ()
。但m ()
来自哪里?我们得到了这个,因为a -> ()
等同于a -> _
,它与(->) a
的类型签名中的类型变量m
统一。
显然,这不是你想要的类型,但你问如何解释错误,所以我会留下它。
答案 3 :(得分:3)
预期类型表示您应该拥有的。在你的情况下,我们有when :: (Monad m) => Bool -> m () -> m ()
。因此编译器推断,k
中的when (n == rn) $ k
应该是a -> ()
类型的。
推断类型表示编译器为变量推断的实际类型。在您的情况下,我们有callCC :: (MonadCont m) => ((a -> m b) -> m a) -> m a
。这意味着它接收的匿名函数类型为(a -> m b) -> m a
。由于k
是此函数的第一个参数,因此编译器得出结论k
似乎类型为a -> m b
。
由于这两种类型不匹配,您会收到错误。
答案 4 :(得分:2)
在这种情况下的错误是,您没有从k
返回值继续callCC
。
更改
when (n == rn) $ k
到
when (n == rn) $ k ()
诀窍。