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

标签 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)

现在他们在笔记中指出了这一点:

Notice that we didn't have to do random gen :: (Bool, StdGen). That's because we already specified that we want booleans in the type declaration of the function. That's why Haskell can infer that we want a boolean value in this case.



太棒了。

现在,当我在 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 编译器可以从我们提供的类型推断出我们想要一个 bool 值,但 ghci 不能。

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

最佳答案

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

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

关于haskell - 为什么 haskell 编译器可以推断出这种类型,而 ghci 不能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48006676/

相关文章:

haskell - Haskell 中的列表连接

haskell - Haskell 中是否有通配符类型变量?

haskell - 我怎样才能找到包含我的包的堆栈解析器?

haskell - 这个特定的递归可以用尾部优化的方式重写吗?

haskell - 使用 Haskell 一次导入整个模块

haskell - 如何让 GHCI 释放内存

haskell - 用于显示类型类函数特化的 GHCi 语法

haskell - 在 ghci 中读取 vs map

haskell - MapM 在 haskell 中的结果?

haskell - 从 ghci session 中的子目录导入(从 yesod 中的测试导入模块)