功能getRandomR
没有做IO
。它不要求做IO
生成随机数一旦你有一个种子。该Rand
单子在MonadRandom
以种子初始化,即可以是一个您提供用于测试目的或一个从IO使用evalRandIO
拉。 Rand
Monad可以在不执行IO
操作的情况下执行此操作,方法是利用random
程序包的System.Random
中显示的纯函数,例如random
和randomR
。这些函数中的每一个都带有一个生成器g
并返回一个新的生成器和一个所需类型的随机值。在内部,Rand
单子其实只是State
单子,而它的状态是发电机g
。
然而,值得注意的是,IO
单子是MonadRandom
,它不使用纯态的功能,它使用正常IO
功能,如randomIO
一个实例是很重要的。您可以使用IO
和Rand
互换,但后者会多一点效率的(不必每次执行系统调用),你可以用于测试目的以获得可重复的结果已知值的种子吧。
因此,要回答你的问题
一个如何判断这个computeSomething
功能将是纯或不纯?
对于这个定义的computeSomething
,这既不是纯或不纯直到MonadRandom
实例解析。如果我们把“纯粹”是“不IO”和“不纯”是“IO”(which is not entirely accurate, but a close approximation),然后computeSomething
可以在其他一些情况下,与不纯的纯洁,就像函数liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r
可以在IO
单子使用或在Maybe
或[]
Monads上。换句话说:
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
或者
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
获得每次调用一个新的随机数生成器。所有这一切都是可能的,而无需改变线路的比它是如何调用main
其他代码的computeSomething
的定义并没有这3种用途之间进行切换。
这是一个'class'没有实现。你可以实例化一些纯粹的“随机”生成器(例如固定种子,通过arg,...)。最后的'm' monad将决定最终的纯度。 – josejuan
实际上,getRandomR的类型是(MonadRandom米,随机A)=>(A,A) - > m是一。 –
@HonzaPokorny我已经添加了一些编辑到我的答案,可能会更好的为您的具体问题。 – bheklilr