只是学习如何对单子和变压器有更深入的直观了解;对我来说,很多看似显而易见的事情仍然有些棘手,哈哈。
因此,我有一个计算驻留在Rand
monad中,但在其中,还有另一个“子计算”(或多个)存在于ST
monad(或State
monad)中,这很重要。 .. ST
仅用于性能,但我认为State
在这种情况下同样有效。
整个计算不需要在ST
monad中...并且此子计算将以不同的起始状态多次调用,因此我不想将整个内容强制为ST
(除非这是惯用的方式)。
没有随机性,结构如下所示:
main = print mainComp
mainComp :: Int
mainComp = otherComp + (subComp 1) + (subComp 2)
subComp :: Int -> Int
subComp n = runST $ do
-- generate state based on n
-- ...
replicateM_ 100 mutateState
-- ...
-- eventually returns an ST s Int
mutateState :: ST s ()
mutateState = -- ...
基本上,一切工作都很好,并且
mainComp
和subComp
中具有完全的引用透明性。到目前为止,这就是我使用
Rand
的方式-main = (evalRandIO mainComp) >>= print
mainComp :: (RandomGen g) => Rand g Int
mainComp = do
subResultA <- subComp 1
subResultB <- subComp 2
return $ otherComp + subResultA + subResultB
subComp :: (RandomGen g) => Int -> Rand g Int
subComp = return $ runST $ do -- is this ok to just throw in return?
-- generate state based on n
-- ...
replicateM_ 100 mutateState
-- ...
-- eventually returns an ST s Int (??)
mutateState :: ??
mutateState = ??
如果我想在其中使用随机种子和
mutateState
单子,那么Rand
应该是什么类型?我想我可能想使用RandT g (ST s) ()
的返回类型,但是如何使它与runST
中的subComp
期望的类型相符? 最佳答案
使用monad变压器时,可以按照添加它们的相反顺序“剥离”层。因此,如果您具有RandT g (ST s) ()
类型的内容,则首先使用evalRandT
或runRandT
消除RandT,然后再调用runST
。
这是结合RandT
和ST
的简单示例:
import Data.STRef
import System.Random
import Control.Monad
import Control.Monad.Trans
import Control.Monad.ST
import Control.Monad.Random
import Control.Monad.Random.Class
stNrand :: RandT StdGen (ST s) Int
stNrand = do
ref <- lift $ newSTRef 0
i <- getRandomR (0,10)
lift $ writeSTRef ref i
lift $ readSTRef ref
main :: IO ()
main = putStrLn . show $ runST $ evalRandT stNrand (mkStdGen 77)
编辑:这是一个扩展版本,现在带有功能
runSTBelowRand
,可让您将RandT StdGen (ST s) a
计算嵌入到Rand StdGen a
计算中。该函数使用getSplit拆分全局计算的种子,并将新的种子馈送到子计算。{-# LANGUAGE RankNTypes #-}
import Data.STRef
import System.Random
import Control.Monad
import Control.Monad.Trans
import Control.Monad.ST
import Control.Monad.Random
import Control.Monad.Random.Class
stNrand :: RandT StdGen (ST s) Int
stNrand = do
ref <- lift $ newSTRef 0
i <- getRandomR (0,10)
lift $ writeSTRef ref i
lift $ readSTRef ref
runSTBelowRand :: (forall s. RandT StdGen (ST s) a) -> Rand StdGen a
runSTBelowRand r = do
splittedSeed <- getSplit
return $ runST $ evalRandT r splittedSeed
globalRand :: Rand StdGen (Int,Int)
globalRand = do
i1 <- runSTBelowRand stNrand
-- possibly non-ST stuff here
i2 <- runSTBelowRand stNrand
return (i1,i2)
main :: IO ()
main = putStrLn . show $ evalRand globalRand (mkStdGen 77)