功能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