为什么haskell编译器可以推断出这种类型,但ghci不能?

时间:2017-12-28 11:30:49

标签: haskell ghci

我正在通过following book学习Haskell - 特别关注randomness的章节:

我正在运行以下文件three-coins.hs

import System.Random 

threeCoins :: StdGen -> (Bool, Bool, Bool)  
threeCoins gen =   
    let (firstCoin, newGen) = random gen
        (secondCoin, newGen') = random newGen
        (thirdCoin, newGen'') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)

main = print ( threeCoins (mkStdGen 21) )

然后我使用runhaskell three-coins.hs执行并获得类似于:

的输出
(True,True,True)

现在他们在笔记中说明了一点:

  

请注意,我们不必执行random gen :: (Bool, StdGen)。那是因为我们已经指定我们在函数的类型声明中需要布尔值。这就是为什么Haskell可以推断我们在这种情况下需要一个布尔值。

很酷。

现在,我使用以下代码在ghci中运行此代码:

import System.Random 

:{
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (firstCoin, newGen) = random gen
        (secondCoin, newGen') = random newGen
        (thirdCoin, newGen'') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)
:}

我收到以下回复:

<interactive>:6:9: error:
    • Ambiguous type variable ‘t0’
      prevents the constraint ‘(Random t0)’ from being solved.
    • When checking that the inferred type
        newGen :: forall t. Random t => StdGen
      is as general as its inferred signature
        newGen :: StdGen
      In the expression:
        let
          (firstCoin, newGen) = random gen
          (secondCoin, newGen') = random newGen
          (thirdCoin, newGen'') = random newGen'
        in (firstCoin, secondCoin, thirdCoin)
      In an equation for ‘threeCoins’:
          threeCoins gen
            = let
                (firstCoin, newGen) = random gen
                (secondCoin, newGen') = random newGen
                ....
              in (firstCoin, secondCoin, thirdCoin)

哪个有趣。有点像他们在书中警告我们的错误。

因此,如果我们修改代码以将类型提示放入:

import System.Random 

:{
threeCoins :: StdGen -> (Bool, Bool, Bool)
threeCoins gen =
    let (firstCoin, newGen) = random gen :: (Bool, StdGen)
        (secondCoin, newGen') = random newGen :: (Bool, StdGen)
        (thirdCoin, newGen'') = random newGen' :: (Bool, StdGen)
    in  (firstCoin, secondCoin, thirdCoin)
:}

工作正常 - 我们可以用以下方法测试它:

threeCoins (mkStdGen 21) 

并获得此结果

(True,True,True)
嗯 - 这很有效。所以Haskell编译器可以从我们提供的类型推断出我们想要一个布尔值,但是ghci不能。

我的问题是:为什么haskell编译器可以推断出这种类型,但ghci不能?

1 个答案:

答案 0 :(得分:3)

正如chi已经评论过的,此代码仅在启用monomorphism restriction时有效。这种限制使得编译器为任何非函数定义选择一个特定类型,即其中没有类型变量的签名,如a中的length :: [a] -> Int。所以(除非你手动指定了一个本地签名),编译器会在它选择之前到处查看这个类型的提示。在您的示例中,它看到firstCoin secondCoin thirdCoin用于最终结果,在顶级签名中声明(Bool, Bool, Bool),因此它推断出所有硬币必须有Bool类型。

在这样一个简单的例子中,这很好,但是在现代的Haskell中,你经常需要更通用的值,所以你可以在多个不同类型的上下文中使用它们或者作为Rank-2函数的参数。你总是可以通过给出明确的签名来实现这一点,但特别是在GHCi中这很尴尬(它通常被称为“ Dreaded 单态限制”),因此在几个版本之前决定默认禁用它GHCI。

概念firstCoin secondCoin thirdCoin等也可能比Bool更通用:random毕竟能够产生任何合适类型的随机值(即具有Random实例的任何类型)。因此原则上,本地定义可以具有多态类型,如下所示:

threeCoins :: StdGen -> (Bool, Bool, Bool)  
threeCoins gen =   
    let firstCoin, secondCoin, thirdCoin :: Random r => r
        (firstCoin, newGen) = random gen
        (secondCoin, newGen') = random newGen
        (thirdCoin, newGen'') = random newGen'
    in  (firstCoin, secondCoin, thirdCoin)

这基本上是关闭单态限制时发生的情况,正如您可以通过使用行编译原始示例所看到的那样

{-# LANGUAGE NoMonomorphismRestriction #-}

在上面。

麻烦的是,您的代码实际上并不适用于那些常规本地签名。原因是有点涉及,基本上r变量的类型信息必须先传播回元组才能在random生成器中使用,原因我现在不明白,Hindley-Milner型系统也做不到。

最好的解决方案是执行此手动元组解包,这无论如何都很尴尬,而是使用random monad,如下所示:

import System.Random 
import Data.Random 

threeCoins :: RVar (Bool, Bool, Bool)  
threeCoins = do   
    firstCoin <- uniform False True
    secondCoin <- uniform False True
    thirdCoin <- uniform False True
    return (firstCoin, secondCoin, thirdCoin)

main = print . sampleState threeCoins $ mkStdGen 21

有或没有同态限制,因为firstCoin secondCoin thirdCoin现在来自monadic bind,which is always monomorphic

顺便说一句,因为你是一个monad然后你可以使用标准组合器,因此很容易将其缩短为

import Control.Monad (replicateM)

threeCoins :: RVar (Bool, Bool, Bool)  
threeCoins = do   
    [firstCoin,secondCoin,thirdCoin] <- replicateM 3 $ uniform False True
    return (firstCoin, secondCoin, thirdCoin)