- Change RandomizedMonad to bind by threading a single RandomGen instead of
authorMatt McCutchen <matt@mattmccutchen.net>
Sat, 2 Aug 2008 01:06:17 +0000 (21:06 -0400)
committerMatt McCutchen <matt@mattmccutchen.net>
Sat, 2 Aug 2008 01:06:17 +0000 (21:06 -0400)
splitting the RandomGen.  Threading satisfies the monad laws, and the
parallelizability and evolvability of splitting can still be explicitly
requested via `msplit'.  I think this is a much more logical design!

- Take the opportunity to flesh out independent-list offerings and add comments.

program/RandomizedMonad.hs

index f3b50d4..b58727c 100644 (file)
@@ -1,32 +1,43 @@
 module RandomizedMonad (
        Randomized,
-       runRandom, runRandomStd, runRandomNewStd,
+       msplit,
+       runRandom1, runRandom, runRandomStd, runRandomNewStd,
        mrandomR, mrandom,
        withProb,
        filterRandomized,
-       indRandomArray
+       indReplicateRandom, indRepeatRandom, indRandomArray
 ) where
 import System.Random
 import Data.Array.IArray
 import Data.Ix
 
 -- Needs -XRank2Types
-newtype Randomized a = Randomized (forall g. RandomGen g => (g -> a))
+newtype Randomized a = Randomized (forall g. RandomGen g => (g -> (a, g)))
 
--- This implementation splits the RandomGen over and over.
--- It would also be possible to serialize everything and use a single RandomGen.
+-- This implementation threads a single RandomGen through the whole process in
+-- order to satisfy the monad laws.
 instance Monad Randomized where
        ma >>= amb = Randomized (\g -> let
-                       (g1, g2) = split g
                        Randomized fa = ma
-                       a = fa g1
+                       (a, g2) = fa g
                        Randomized fb = amb a
                        in fb g2
                )
-       return x = Randomized (const x)
+       return x = Randomized (\g -> (x, g))
+
+-- Splits the generator and runs the argument on the left generator while
+-- threading the right generator on.  C.f. unsaveInterleaveIO.  Use this to
+-- make a sub-calculation parallelizable and evolvable without breaking
+-- same-seed reproducibility of the whole calculation.
+msplit :: Randomized a -> Randomized a
+msplit (Randomized fa) = Randomized
+       (\g -> let (g1, g2) = split g in (fst (fa g1), g2))
+
+runRandom1 :: RandomGen g => g -> Randomized a -> (a, g)
+runRandom1 g (Randomized fa) = fa g
 
 runRandom :: RandomGen g => g -> Randomized a -> a
-runRandom g (Randomized fa) = fa g
+runRandom g (Randomized fa) = fst (fa g)
 
 -- Conveniences
 runRandomStd :: Randomized a -> IO a
@@ -40,10 +51,10 @@ runRandomNewStd ra = do
        return $ runRandom g ra
 
 -- Monadic versions of random and randomR (to generate primitive-ish values)
-mrandomR :: Random a => (a, a) -> Randomized a
-mrandomR lohi = Randomized (\g -> fst (randomR lohi g))
 mrandom :: Random a => Randomized a
-mrandom = Randomized (\g -> fst (random g))
+mrandom = Randomized random
+mrandomR :: Random a => (a, a) -> Randomized a
+mrandomR lohi = Randomized $ randomR lohi
 
 chooseCase :: Double -> [(Double, a)] -> a -> a
 chooseCase val ifCs elseR = case ifCs of
@@ -52,6 +63,7 @@ chooseCase val ifCs elseR = case ifCs of
                then theR
                else chooseCase (val - cutoff) ifCt elseR
 
+-- An if-elsif...else-style construct where each "if" has a probability.
 withProb :: [(Double, Randomized a)] -> Randomized a -> Randomized a
 withProb ifCs elseR = do
        val <- mrandom
@@ -63,10 +75,20 @@ filterRandomized f ra = do
        a <- ra
        if f a then return a else filterRandomized f ra
 
--- Randomized array with elements chosen independently following a given
--- randomized element.
+-- A randomized list of elements chosen independently from a distribution.
+-- Each element is under msplit for parallelizability.
+indReplicateRandom :: Int -> Randomized a -> Randomized [a]
+indReplicateRandom n ra = sequence $ replicate n $ msplit ra
+
+-- An infinite randomized list of elements chosen independently from a
+-- distribution.  The list is under msplit to avoid an infinite loop when it is
+-- bound.
+indRepeatRandom :: Randomized a -> Randomized [a]
+indRepeatRandom ra = msplit $ sequence $ repeat $ msplit ra
+
+-- Produces an array of elements chosen independently from a distribution.
 indRandomArray :: (IArray a e, Ix i) =>
        (i, i) -> Randomized e -> Randomized (a i e)
-indRandomArray bds rElement = do
-       list <- sequence $ replicate (rangeSize bds) rElement
+indRandomArray bds re = do
+       list <- indReplicateRandom (rangeSize bds) re
        return (listArray bds list)