如何理解Haskell编译器消息

Dfr*_*Dfr 5 haskell types type-inference ghc

美好的一天.

这是简单的"猜数"片段,它包含单个错误,但编译器使得很难解决错误:

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)
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

它让我真的很困惑,它告诉某些预期类型'()',但如何发现谁是这个"东西"?是"k"吗?似乎并非如此.如果我们交换预期和推断它看起来更健康,但它现在看起来如何,它是非常令人困惑的.我的问题是:如何发现原因并修复此错误?

Dav*_*ani 10

要了解这些类型,最好查看周围的函数.

错误提到变量k,该变量首先出现在表达式中callCC $ \k -> forever ....我们可以通过查看以下类型来获取k的类型callCC:

callCC :: MonadCont m => ((a -> m b) -> m a) -> m a
Run Code Online (Sandbox Code Playgroud)

从那以后,我们可以看到k有类型a -> m b.请注意,由于b该函数中的任何其他位置都没有使用,因此它的类型无关紧要,并且将由使用该函数的上下文确定.

k正在被用在when表达式之后$(实际上并不需要).何时的类型是:

 when :: Monad m => Bool -> m () -> m ()
Run Code Online (Sandbox Code Playgroud)

注意第二个参数需要一个m (),但你是在K,其类型为合格a -> m b(因为b无关紧要它可以比拟().所以,很显然一些参数需要被给予k.为了弄清楚什么是,我们在定义回首callCC.那个arg是forever $ do ...程序中的值.

看着永远的类型:

forever :: Monad m => m a -> m b
Run Code Online (Sandbox Code Playgroud)

它需要一个monadic计算m a,结果返回另一个monadic计算m b.注意如何b不出现在参数中forever.这意味着类型由调用它的上下文决定(例如read "3"可以是类型DoubleInt取决于它所在的表达式).这取决于runContT:

runContT :: ContT r m a -> (a -> m r) -> m r
Run Code Online (Sandbox Code Playgroud)

如果你匹配类型变量runContT,callCCforever,你会注意到bin forever对应于ain runContT.将a在第二个参数使用runContT,这在你的程序return.return有类型a -> m a,所以类型与程序中的类型a相同r.r出现在输出中m r.

runContT表达在一做上下文中,而没有任何绑定(<-).所以你的代码等同于:

main = do
  rn <- randomRIO (1,10) :: IO Int
  runContT (callCC ....) (return) >> (putStrLn $ "Good guess! " ++ (show rn))
Run Code Online (Sandbox Code Playgroud)

通过查看以下类型,最终可以解决这个谜团>>:

(>>) :: Monad m => m a -> m b -> m b
Run Code Online (Sandbox Code Playgroud)

>>丢弃传递给它的第一个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)
Run Code Online (Sandbox Code Playgroud)

所以这是一个非常难的例子,我理解为什么你没有遵循它.尽管如此,你确实会有更好的经验.此外,延续monad是非常先进和复杂的haskell.


Don*_*art 9

GHC警告消息中要关注的关键事项是:

你有类型; 它需要的类型

Couldn't match expected type `()' against inferred type `m b'
Run Code Online (Sandbox Code Playgroud)

所以你有一种类型的东西(),在monadic环境中m b.

什么表达是错的

In the second argument of `($)', namely `k'
Run Code Online (Sandbox Code Playgroud)

所以k有错误的类型.

行号

simple.hs:11:21
Run Code Online (Sandbox Code Playgroud)

在第11行.


C. *_*ann 5

不要过于依赖"预期"与"推断".哪个并不总是显而易见且相关性较低; 重要的是类型检查器有关于某个术语类型的冲突信息.最常见的情况是上下文期望一种类型(例如,应用采用特定类型的参数的函数),而推断该术语的不同类型.

现在,对于你得到的错误:

Couldn't match expected type `()' against inferred type `m b'
Run Code Online (Sandbox Code Playgroud)

这意味着冲突的类型是()m b.请注意,这些不一定是任何实际表达式的完整类型; 只是那个有冲突的部分.

Expected type: a -> ()
Inferred type: a -> m b
Run Code Online (Sandbox Code Playgroud)

这里我们有两种实际类型.这些a ->部分没有冲突,所以上面没有提到.

In the second argument of `($)', namely `k'
Run Code Online (Sandbox Code Playgroud)

这告诉我们发现冲突的上下文,并给出其类型有争议的表达式,即k.

这里的推断类型是它"已经知道"的类型k.它来自哪里?k被绑定为传递给lambda的参数,该lambda callCC具有类型((a -> m b) -> m a) -> m a,因此它是推断类型.

这里的期望类型是第二个参数when,它有类型Bool -> m () -> m (),它给了我们m ().a -> ()但是来自哪里呢?我们得到的,因为a -> _相当于(->) a,它与类型变量结合mwhen的类型签名.

显然,这不是你想要的类型,但你问如何解释错误,所以我会留下它.