【问题标题】:How to understand Haskell compiler messages如何理解 Haskell 编译器消息
【发布时间】:2011-04-21 16:45:54
【问题描述】:

美好的一天。

这是简单的“猜数字”sn-p,它包含单个错误,但编译器 真的很难理解哪里出了问题:

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' 在这里应该是一个很大的提示...

标签: haskell types type-inference ghc


【解决方案1】:

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

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

callCC :: MonadCont m => ((a -> m b) -> m a) -> m a

由此,我们可以看到k 的类型为a -&gt; m b。请注意,由于 b 没有在该函数的其他任何地方使用,因此它的类型无关紧要,将由使用该函数的上下文确定。

k$ 之后的 when 表达式中使用(实际上并不需要)。 when的类型是:

 when :: Monad m => Bool -> m () -> m ()

注意第二个参数需要一个m (),但是你传入了k,它的类型是a -&gt; m b(因为b没关系它可以匹配()。所以显然需要给出一些参数到k。要弄清楚a是什么,我们回顾一下callCC的定义。那个arg是你程序中的值forever $ do ...

看永远的类型:

forever :: Monad m => m a -> m b

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

runContT :: ContT r m a -> (a -> m r) -> m r

如果匹配来自runContTcallCCforever 的类型变量,您会注意到forever 中的b 对应于runContT 中的aa 用于runContT 的第二个参数,在您的程序中是returnreturn 的类型为a -&gt; m a,因此a 的类型与您程序中的r 相同。 r 出现在输出 m r 中。

runContT 表达式在 do 上下文中,没有任何绑定 (&lt;-)。所以你的代码相当于这个:

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

&gt;&gt;的类型终于可以解开谜团了:

(>>) :: Monad m => m a -> m b -> m b

&gt;&gt; 丢弃传递给它的第一个单子计算的值(这是runContT 表达式)。所以计算返回的值实际上并不重要(注意a 不会出现在&gt;&gt; 函数的结果中)。如果你通过这个解释来追溯这个结果,你会意识到传递给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)

所以这是一个非常困难的例子,我理解你为什么不遵循它。不过,您确实会通过经验变得更好。此外,continuation monad 是相当高级和复杂的 haskell。

【讨论】:

  • 你写:"注意第二个参数需要一个m(),但是你传入的是k,它的类型是a -> m b(因为b没关系它可以匹配() . 所以显然需要给 k 一些论据。” 感谢您的高级解释,但这一刻仍然令人困惑。“m()”类型有点神秘,为什么“m b”确实匹配这种类型,因为我们仍然应该有类型不匹配:'()' != 'b'
  • @Dfr:b是一个类型变量,可以表示任何类型(例如Int、Just Int、())。当在 when 语句中使用该表达式时, b 将被锁定为 type () (否则该函数不会进行类型检查)。
  • 为什么 a -> m b 没有通过?因为 a -> m b 可以表示为 "(->) a (m b)" 或 "m (a (m b))",所以它也应该通过类型检查。
  • 因为 (->) 不是类型变量,它是一个带有两个参数的具体类型。所以你不能用 m 替换 (->)。
【解决方案2】:

在 GHC 警告消息中查找的关键内容是:

您拥有的类型;输入它需要是

Couldn't match expected type `()' against inferred type `m b'

所以你有()类型的东西,在一个单子设置m b

哪个表达有问题

In the second argument of `($)', namely `k'

所以k 的类型有误。

行号

simple.hs:11:21

在第 11 行。

【讨论】:

    【解决方案3】:

    不要过于纠结于“预期”与“推断”。哪一个并不总是显而易见的,也不经常相关;重要的是类型检查器具有关于某些术语类型的冲突信息。最常见的情况是上下文期望一种类型(例如,应用一个接受特定类型参数的函数),而为该术语推断出不同的类型。

    现在,对于您遇到的错误:

    Couldn't match expected type `()' against inferred type `m b'
    

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

    Expected type: a -> ()
    Inferred type: a -> m b
    

    这里我们有两种实际类型。 a -&gt; 部分不冲突,所以上面没有提到。

    In the second argument of `($)', namely `k'
    

    这告诉我们发现冲突的上下文,并给出了类型有问题的表达式,即k

    这里推断的类型是它“已经知道”k 的类型。那是从哪里来的? k 绑定为传递给 callCC 的 lambda 的参数,其类型为 ((a -&gt; m b) -&gt; m a) -&gt; m a,因此这是推断的类型。

    这里的预期类型是when 的第二个参数,它的类型为Bool -&gt; m () -&gt; m (),它给了我们m ()。不过,a -&gt; () 来自哪里?我们得到这个是因为a -&gt; _ 等价于(-&gt;) a,它与when 的类型签名中的类型变量m 统一。

    显然这不是您想要的类型,但您询问了如何解释错误,所以我将保留它。

    【讨论】:

      【解决方案4】:

      预期类型表示你应该在那个地方拥有什么。在你的情况下,我们有when :: (Monad m) =&gt; Bool -&gt; m () -&gt; m ()。编译器因此推断,when (n == rn) $ k 中的k 应该是 类型为a -&gt; ()

      推断类型表示编译器为变量推导出的实际类型。在您的情况下,我们有callCC :: (MonadCont m) =&gt; ((a -&gt; m b) -&gt; m a) -&gt; m a。这意味着它接收到的匿名函数是(a -&gt; m b) -&gt; m a 类型的。由于k 是此函数的第一个参数,因此编译器得出结论k 似乎 属于a -&gt; m b 类型。

      由于这两种类型不匹配,所以会出现错误。

      【讨论】:

        【解决方案5】:

        这种情况下的错误是您没有为延续k 提供从callCC 返回的值。

        变化

        when (n == rn) $ k
        

        when (n == rn) $ k ()
        

        成功了。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2023-04-02
          • 1970-01-01
          • 1970-01-01
          • 2020-04-14
          • 2010-12-26
          • 1970-01-01
          • 2012-02-17
          相关资源
          最近更新 更多