Skip to content

Commit 1539b1d

Browse files
committed
Merge pull request #37 from hdgarrood/stack-safe-gen-6
Stack safe gen (StateT)
2 parents 10f973c + 9321aa4 commit 1539b1d

File tree

4 files changed

+71
-58
lines changed

4 files changed

+71
-58
lines changed

bower.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"purescript-exceptions": "^0.3.0",
2525
"purescript-lists": "^0.7.0",
2626
"purescript-random": "^0.2.0",
27-
"purescript-strings": "^0.7.0"
27+
"purescript-strings": "^0.7.0",
28+
"purescript-transformers": "^0.6.1"
2829
}
2930
}

docs/Test/QuickCheck/Gen.md

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,16 @@ type GenState = { newSeed :: Seed, size :: Size }
2020

2121
The state of the random generator monad
2222

23-
#### `GenOut`
24-
25-
``` purescript
26-
type GenOut a = { state :: GenState, value :: a }
27-
```
28-
29-
The output of the random generator monad
30-
3123
#### `Gen`
3224

3325
``` purescript
34-
data Gen a
26+
type Gen a = State GenState a
3527
```
3628

3729
The random generator monad
3830

3931
`Gen` is a state monad which encodes a linear congruential generator.
4032

41-
##### Instances
42-
``` purescript
43-
instance functorGen :: Functor Gen
44-
instance applyGen :: Apply Gen
45-
instance applicativeGen :: Applicative Gen
46-
instance bindGen :: Bind Gen
47-
instance monadGen :: Monad Gen
48-
```
49-
5033
#### `repeatable`
5134

5235
``` purescript
@@ -139,6 +122,14 @@ arrayOf1 :: forall a. Gen a -> Gen (Tuple a (Array a))
139122

140123
Create a random generator which generates a non-empty array of random values.
141124

125+
#### `listOf`
126+
127+
``` purescript
128+
listOf :: forall a. Int -> Gen a -> Gen (List a)
129+
```
130+
131+
Create a random generator which generates a list of random values of the specified size.
132+
142133
#### `vectorOf`
143134

144135
``` purescript
@@ -159,7 +150,7 @@ uniform probability.
159150
#### `runGen`
160151

161152
``` purescript
162-
runGen :: forall a. Gen a -> GenState -> GenOut a
153+
runGen :: forall a. Gen a -> GenState -> Tuple a GenState
163154
```
164155

165156
Run a random generator

src/Test/QuickCheck/Gen.purs

Lines changed: 33 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
module Test.QuickCheck.Gen
44
( Gen()
55
, GenState()
6-
, GenOut()
76
, Size()
87
, repeatable
98
, stateful
@@ -16,6 +15,7 @@ module Test.QuickCheck.Gen
1615
, frequency
1716
, arrayOf
1817
, arrayOf1
18+
, listOf
1919
, vectorOf
2020
, elements
2121
, runGen
@@ -31,14 +31,19 @@ import Prelude
3131

3232
import Control.Monad.Eff (Eff())
3333
import Control.Monad.Eff.Random (RANDOM())
34+
import Control.Monad.State (State(..), runState, evalState)
35+
import Control.Monad.State.Class (state, modify)
36+
import Control.Monad.Rec.Class (MonadRec, tailRecM)
3437
import Data.Array ((!!), length, range)
38+
import Data.Tuple (Tuple(..))
3539
import Data.Foldable (fold)
3640
import Data.Int (fromNumber, toNumber)
3741
import Data.Maybe (fromMaybe)
3842
import Data.Monoid.Additive (Additive(..), runAdditive)
3943
import Data.Traversable (sequence)
4044
import Data.Tuple (Tuple(..), fst, snd)
41-
import Data.List (List(..))
45+
import Data.Either (Either(..))
46+
import Data.List (List(..), fromList)
4247
import Test.QuickCheck.LCG
4348
import qualified Math as M
4449

@@ -49,33 +54,30 @@ type Size = Int
4954
-- | The state of the random generator monad
5055
type GenState = { newSeed :: Seed, size :: Size }
5156

52-
-- | The output of the random generator monad
53-
type GenOut a = { state :: GenState, value :: a }
54-
5557
-- | The random generator monad
5658
-- |
5759
-- | `Gen` is a state monad which encodes a linear congruential generator.
58-
data Gen a = Gen (GenState -> GenOut a)
60+
type Gen a = State GenState a
5961

6062
-- | Create a random generator for a function type.
6163
repeatable :: forall a b. (a -> Gen b) -> Gen (a -> b)
62-
repeatable f = Gen $ \s -> { value: \a -> (runGen (f a) s).value, state: s }
64+
repeatable f = state $ \s -> Tuple (\a -> fst (runGen (f a) s)) s
6365

6466
-- | Create a random generator which uses the generator state explicitly.
6567
stateful :: forall a. (GenState -> Gen a) -> Gen a
66-
stateful f = Gen (\s -> runGen (f s) s)
68+
stateful f = state $ \s -> runGen (f s) s
6769

6870
-- | Modify a random generator by setting a new random seed.
6971
variant :: forall a. Seed -> Gen a -> Gen a
70-
variant n g = Gen $ \s -> runGen g s { newSeed = n }
72+
variant n g = state $ \s -> runGen g s { newSeed = n }
7173

7274
-- | Create a random generator which depends on the size parameter.
7375
sized :: forall a. (Size -> Gen a) -> Gen a
7476
sized f = stateful (\s -> f s.size)
7577

7678
-- | Modify a random generator by setting a new size parameter.
7779
resize :: forall a. Size -> Gen a -> Gen a
78-
resize sz g = Gen $ \s -> runGen g s { size = sz }
80+
resize sz g = state $ \s -> runGen g s { size = sz }
7981

8082
-- | Create a random generator which samples a range of `Number`s i
8183
-- | with uniform probability.
@@ -127,11 +129,21 @@ arrayOf1 g = sized $ \n ->
127129
xs <- vectorOf (k - one) g
128130
return $ Tuple x xs
129131

132+
replicateMRec :: forall m a. (MonadRec m) => Int -> m a -> m (List a)
133+
replicateMRec k _ | k <= 0 = return Nil
134+
replicateMRec k gen = tailRecM go (Tuple Nil k)
135+
where
136+
go :: (Tuple (List a) Int) -> m (Either (Tuple (List a) Int) (List a))
137+
go (Tuple acc 0) = return $ Right acc
138+
go (Tuple acc n) = gen <#> \x -> Left (Tuple (Cons x acc) (n - 1))
139+
140+
-- | Create a random generator which generates a list of random values of the specified size.
141+
listOf :: forall a. Int -> Gen a -> Gen (List a)
142+
listOf = replicateMRec
143+
130144
-- | Create a random generator which generates a vector of random values of a specified size.
131145
vectorOf :: forall a. Int -> Gen a -> Gen (Array a)
132-
vectorOf k g
133-
| k <= 0 = return []
134-
| otherwise = sequence $ const g <$> range one k
146+
vectorOf k g = fromList <$> listOf k g
135147

136148
-- | Create a random generator which selects a value from a non-empty collection with
137149
-- | uniform probability.
@@ -141,12 +153,12 @@ elements x xs = do
141153
pure if n == zero then x else fromMaybe x (xs !! (n - one))
142154

143155
-- | Run a random generator
144-
runGen :: forall a. Gen a -> GenState -> GenOut a
145-
runGen (Gen f) = f
156+
runGen :: forall a. Gen a -> GenState -> Tuple a GenState
157+
runGen = runState
146158

147159
-- | Run a random generator, keeping only the randomly-generated result
148160
evalGen :: forall a. Gen a -> GenState -> a
149-
evalGen gen st = (runGen gen st).value
161+
evalGen = evalState
150162

151163
-- | Sample a random generator
152164
sample :: forall r a. Seed -> Size -> Gen a -> Array a
@@ -164,8 +176,8 @@ randomSample = randomSample' 10
164176

165177
-- | A random generator which simply outputs the current seed
166178
lcgStep :: Gen Int
167-
lcgStep = Gen f where
168-
f s = { value: runSeed s.newSeed, state: s { newSeed = lcgNext s.newSeed } }
179+
lcgStep = state f where
180+
f s = Tuple (runSeed s.newSeed) (s { newSeed = lcgNext s.newSeed })
169181

170182
-- | A random generator which approximates a uniform random variable on `[0, 1]`
171183
uniform :: Gen Number
@@ -175,25 +187,8 @@ foreign import float32ToInt32 :: Number -> Int
175187

176188
-- | Perturb a random generator by modifying the current seed
177189
perturbGen :: forall a. Number -> Gen a -> Gen a
178-
perturbGen n (Gen f) = Gen $ \s -> f (s { newSeed = perturb s.newSeed })
190+
perturbGen n gen = do
191+
modify \s -> s { newSeed = perturb s.newSeed }
192+
gen
179193
where
180194
perturb oldSeed = mkSeed (runSeed (lcgNext (mkSeed (float32ToInt32 n))) + runSeed oldSeed)
181-
182-
instance functorGen :: Functor Gen where
183-
map f (Gen g) = Gen $ \s -> case g s of
184-
{ value = value, state = state } -> { value: f value, state: state }
185-
186-
instance applyGen :: Apply Gen where
187-
apply (Gen f) (Gen x) = Gen $ \s ->
188-
case f s of
189-
{ value = f', state = s' } -> case x s' of
190-
{ value = x', state = s'' } -> { value: f' x', state: s'' }
191-
192-
instance applicativeGen :: Applicative Gen where
193-
pure a = Gen (\s -> { value: a, state: s })
194-
195-
instance bindGen :: Bind Gen where
196-
bind (Gen f) g = Gen $ \s -> case f s of
197-
{ value = value, state = state } -> runGen (g value) state
198-
199-
instance monadGen :: Monad Gen

test/Main.purs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
2+
module Test.Main where
3+
4+
import Prelude
5+
import Control.Bind
6+
import Data.Array (head)
7+
import Data.Maybe.Unsafe (fromJust)
8+
import Data.Foldable
9+
import Test.QuickCheck.Gen
10+
import Test.QuickCheck.Arbitrary
11+
import Control.Monad.Eff.Console
12+
13+
main = do
14+
log "Try with some little Gens first"
15+
print =<< go 10
16+
print =<< go 100
17+
print =<< go 1000
18+
print =<< go 10000
19+
20+
log "Testing stack safety of Gen"
21+
print =<< go 20000
22+
print =<< go 100000
23+
24+
where
25+
go n = map (sum <<< unsafeHead) $ randomSample' 1 (vectorOf n (arbitrary :: Gen Int))
26+
unsafeHead = fromJust <<< head

0 commit comments

Comments
 (0)