Quick hacks to try to get this working again in 2021.
[match/match.git] / program / RandomizedMonad.hs
... / ...
CommitLineData
1module RandomizedMonad (
2 Randomized,
3 msplit,
4 runRandom1, runRandom, runRandomStd, runRandomNewStd,
5 mrandomR, mrandom,
6 withProb, withWeight,
7 filterRandomized,
8 indReplicateRandom, indRepeatRandom, indRandomArray
9) where
10import Control.Monad
11import System.Random
12import Data.Array.IArray
13import Data.Ix
14
15-- Needs -XRank2Types
16newtype Randomized a = Randomized (forall g. RandomGen g => (g -> (a, g)))
17
18-- This implementation threads a single RandomGen through the whole process in
19-- order to satisfy the monad laws.
20
21-- Migrate according to the guide at
22-- https://gitlab.haskell.org/ghc/ghc/-/wikis/migration/7.10#ghc-says-no-instance-for-applicative-
23-- ~ 2021-08-12
24instance Functor Randomized where
25 fmap = liftM
26
27instance Applicative Randomized where
28 pure x = Randomized (\g -> (x, g))
29 (<*>) = ap
30
31instance Monad Randomized where
32 ma >>= amb = Randomized (\g -> let
33 Randomized fa = ma
34 (a, g2) = fa g
35 Randomized fb = amb a
36 in fb g2
37 )
38
39-- Splits the generator and runs the argument on the left generator while
40-- threading the right generator on. C.f. unsaveInterleaveIO. Use this to
41-- make a sub-calculation parallelizable and evolvable without breaking
42-- same-seed reproducibility of the whole calculation.
43msplit :: Randomized a -> Randomized a
44msplit (Randomized fa) = Randomized
45 (\g -> let (g1, g2) = split g in (fst (fa g1), g2))
46
47runRandom1 :: RandomGen g => g -> Randomized a -> (a, g)
48runRandom1 g (Randomized fa) = fa g
49
50runRandom :: RandomGen g => g -> Randomized a -> a
51runRandom g (Randomized fa) = fst (fa g)
52
53-- Conveniences
54runRandomStd :: Randomized a -> IO a
55runRandomStd ra = do
56 g <- getStdGen
57 return $ runRandom g ra
58
59runRandomNewStd :: Randomized a -> IO a
60runRandomNewStd ra = do
61 g <- newStdGen
62 return $ runRandom g ra
63
64-- Monadic versions of random and randomR (to generate primitive-ish values)
65mrandom :: Random a => Randomized a
66mrandom = Randomized random
67mrandomR :: Random a => (a, a) -> Randomized a
68-- Eta-expand this one to keep GHC 6.6.1 on birdy happy.
69mrandomR lohi = Randomized (\g -> randomR lohi g)
70
71chooseCase :: Double -> [(Double, a)] -> a -> a
72chooseCase val ifCs elseR = case ifCs of
73 [] -> elseR
74 (cutoff, theR):ifCt -> if val < cutoff
75 then theR
76 else chooseCase (val - cutoff) ifCt elseR
77
78-- An if-elsif...else-style construct where each "if" has a probability.
79withProb :: [(Double, Randomized a)] -> Randomized a -> Randomized a
80withProb ifCs elseR = do
81 val <- mrandom
82 chooseCase val ifCs elseR
83
84-- Like withProb, but without an else case and with the "probabilities" scaled
85-- so that they sum to 1.
86withWeight :: [(Double, Randomized a)] -> Randomized a
87withWeight ifCs = do
88 val <- mrandomR (0, sum (map fst ifCs))
89 chooseCase val (tail ifCs) (snd (head ifCs))
90
91-- Keep trying until we get what we want.
92filterRandomized :: (a -> Bool) -> Randomized a -> Randomized a
93filterRandomized f ra = do
94 a <- ra
95 if f a then return a else filterRandomized f ra
96
97-- A randomized list of elements chosen independently from a distribution.
98-- Each element is under msplit for parallelizability.
99indReplicateRandom :: Int -> Randomized a -> Randomized [a]
100indReplicateRandom n ra = sequence $ replicate n $ msplit ra
101
102-- An infinite randomized list of elements chosen independently from a
103-- distribution. The list is under msplit to avoid an infinite loop when it is
104-- bound.
105indRepeatRandom :: Randomized a -> Randomized [a]
106indRepeatRandom ra = msplit $ sequence $ repeat $ msplit ra
107
108-- Produces an array of elements chosen independently from a distribution.
109indRandomArray :: (IArray a e, Ix i) =>
110 (i, i) -> Randomized e -> Randomized (a i e)
111indRandomArray bds re = do
112 list <- indReplicateRandom (rangeSize bds) re
113 return (listArray bds list)