我最近了解了MonadRandom库。它为您提供了一个名为getRandomR
的函数,其类型签名为:
getRandomR :: (MonadRandom m, Random a) => (a, a) -> m a
显然,您可以编写一个使用getRandomR
的函数,该类型的签名不包含IO
的任何内容。
computeSomething :: MonadRandom m => Int -> m Int
computeSomething a = getRandomR (0, a)
根据呼叫者的不同,m
实例将被填写。如果它从IO
上下文运行,则该函数将是不纯的。
所以,问题是:一个没有声称做IO
的函数怎么能真正做IO
?如何判断这个computeSomething
函数是纯粹的还是不纯的?
答案 0 :(得分:14)
函数getRandomR
没有IO
。一旦有种子,就不需要IO
生成随机数。 Rand
中的MonadRandom
Monad使用种子进行初始化,该种子可以是您为测试目的提供的种子,也可以是使用evalRandIO
从IO中提取的种子。 Rand
Monad可以通过利用IO
包中的System.Random
中公开的纯函数(例如random
和{{1})执行random
操作,从而执行此操作}}。这些函数中的每一个都使用生成器randomR
并返回一个新生成器和所需类型的随机值。在内部,g
Monad实际上只是Rand
Monad,其状态是生成器State
。
但是,重要的是要注意g
Monad是IO
的一个实例,而不是使用纯状态函数,它使用正常的MonadRandom
函数,如{{ 1}}。您可以互换使用IO
和randomIO
,但后者效率会更高(每次都不必执行系统调用),您可以使用已知值为其播种测试目的是为了获得可重复的结果。
所以回答你的问题
如何判断这个
IO
函数是纯粹的还是不纯的?
对于Rand
的这个定义,在解析computeSomething
的实例之前,它既不纯粹也不纯洁。如果我们采取"纯"是"不是IO"并且"不纯的"成为" IO" (which is not entirely accurate, but a close approximation),然后computeSomething
在某些情况下可能是纯粹的,而在其他情况下则不纯,就像函数MonadRandom
可以在computeSomething
Monad或{{liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
上使用一样1}}或IO
Monads。换句话说:
Maybe
将始终返回[]
,因此可以将其视为纯,而
liftM2 (+) (Just 1) (Just 2)
不会总是返回相同的东西。虽然Just 3
的每个预定义实例都被认为是不纯的(liftM2 (++) getLine getLine
且MonadRandom
具有内部状态,因此它们在技术上是不纯的),您可以使用实例提供自己的数据类型在调用RandT
或其他Rand
函数时始终返回相同值的MonadRandom
。出于这个原因,我会说getRandom
本身并不纯粹或不纯洁。
也许有些代码可以帮助解释它(简化,我跳过MonadRandom
转换器):
MonadRandom
所以我们有一个功能
RandT
然后我们可以将它用作
import Control.Monad.State
import qualified System.Random as R
class MonadRandom m where
getRandom :: Random a => m a
getRandoms :: Random a => m [a]
getRandomR :: Random a => (a, a) -> m a
getRandomRs :: Random a => (a, a) -> m [a]
-- Not the real definition, the MonadRandom library defines a RandT
-- Monad transformer where Rand g a = RandT g Identity a, with
-- newtype RandT g m a = RandT (StateT g m a), but I'm trying to
-- keep things simple for this example.
newtype Rand g a = Rand { unRand :: State g a }
instance Monad (Rand g) where
-- Implementation isn't relevant here
instance RandomGen g => MonadRandom (Rand g) where
getRandom = state R.random
getRandoms = sequence $ repeat getRandom
getRandomR range = state (R.randomR range)
getRandomRs range = sequence $ repeat $ getRandomR range
instance MonadRandom IO where
getRandom = R.randomIO
getRandoms = sequence $ repeat getRandom
getRandomR range = R.randomRIO range
getRandomRs range = sequence $ repeat $ getRandomR range
或者
computeSomething :: MonadRandom m => Int -> m Int
computeSomething high = getRandomR (0, high)
或者,如果您想使用已知的生成器进行测试:
main :: IO ()
main = do
i <- computeSomething 10
putStrLn $ "A random number between 0 and 10: " ++ show i
在最后一种情况下,它每次都会打印相同的数字,并随机生成一个&#34;随机的&#34;过程确定性和纯粹。这使您能够重新运行通过提供显式种子来生成随机数的实验,或者您可以传入系统的随机生成器一次,或者您可以使用直接main :: IO ()
main = do
-- evalRandIO uses getStdGen and passes the generator in for you
i <- evalRandIO $ computeSomething 10
putStrLn $ "A random number between 0 and 10: " ++ show i
来获取新的随机发电机每次通话。所有这一切都是可能的,而不必更改除main :: IO ()
main = do
let myGen = R.mkStdGen 12345
i = evalRand (computeSomething 10) myGen
putStrLn $ "A random number between 0 and 10: " ++ show i
中调用的代码之外的代码行,IO
的定义在这3个用途之间不会发生变化。