我正在处理 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 时有效。已启用。该限制使编译器为任何非函数定义选择一种特定类型,即其中没有类型变量的签名,如 a
在 length :: [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/