我正在通过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不能?
答案 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)