Skip to content

Commit 89a767e

Browse files
committed
Use a newtype for Seed
1 parent 5342def commit 89a767e

5 files changed

Lines changed: 69 additions & 27 deletions

File tree

docs/Test/QuickCheck.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ representing the number of tests which should be run.
4949
#### `quickCheckPure`
5050

5151
``` purescript
52-
quickCheckPure :: forall prop. (Testable prop) => Int -> Int -> prop -> List Result
52+
quickCheckPure :: forall prop. (Testable prop) => Seed -> Int -> prop -> List Result
5353
```
5454

5555
Test a property, returning all test results as an array.

docs/Test/QuickCheck/LCG.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
## Module Test.QuickCheck.LCG
22

3-
#### `Seed`
4-
5-
``` purescript
6-
type Seed = Int
7-
```
8-
93
#### `lcgM`
104

115
``` purescript
@@ -28,12 +22,15 @@ The *increment*: a magic constant for the linear congruential generator
2822
lcgN :: Int
2923
```
3024

31-
The *modulus*: a magic constant for the linear congruential generator
25+
The *modulus*: a magic constant for the linear congruential generator.
26+
It is equal to 2^31 - 1, a Mersenne prime. It is useful for this value to
27+
be prime, because then the requirement of the initial seed being coprime
28+
to the modulus is satisfied when the seed is between 1 and lcgN - 1.
3229

3330
#### `lcgNext`
3431

3532
``` purescript
36-
lcgNext :: Int -> Int
33+
lcgNext :: Seed -> Seed
3734
```
3835

3936
Step the linear congruential generator
@@ -46,4 +43,32 @@ randomSeed :: forall e. Eff (random :: RANDOM | e) Seed
4643

4744
Create a random seed
4845

46+
#### `Seed`
47+
48+
``` purescript
49+
newtype Seed
50+
```
51+
52+
A seed for the linear congruential generator. We omit a `Semiring`
53+
instance because there is no `zero` value, as 0 is not an acceptable
54+
seed for the generator.
55+
56+
##### Instances
57+
``` purescript
58+
instance showSeed :: Show Seed
59+
instance eqSeed :: Eq Seed
60+
```
61+
62+
#### `mkSeed`
63+
64+
``` purescript
65+
mkSeed :: Int -> Seed
66+
```
67+
68+
#### `runSeed`
69+
70+
``` purescript
71+
runSeed :: Seed -> Int
72+
```
73+
4974

src/Test/QuickCheck.purs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ quickCheck' n prop = do
6565
-- |
6666
-- | The first argument is the _random seed_ to be passed to the random generator.
6767
-- | The second argument is the number of tests to run.
68-
quickCheckPure :: forall prop. (Testable prop) => Int -> Int -> prop -> List Result
68+
quickCheckPure :: forall prop. (Testable prop) => Seed -> Int -> prop -> List Result
6969
quickCheckPure s n prop = evalGen (replicateM n (test prop)) { newSeed: s, size: 10 }
7070

7171
-- | The `Testable` class represents _testable properties_.

src/Test/QuickCheck/Gen.purs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ randomSample = randomSample' 10
165165
-- | A random generator which simply outputs the current seed
166166
lcgStep :: Gen Int
167167
lcgStep = Gen f where
168-
f s = { value: s.newSeed, state: s { newSeed = lcgNext s.newSeed } }
168+
f s = { value: runSeed s.newSeed, state: s { newSeed = lcgNext s.newSeed } }
169169

170170
-- | A random generator which approximates a uniform random variable on `[0, 1]`
171171
uniform :: Gen Number
@@ -175,7 +175,9 @@ foreign import float32ToInt32 :: Number -> Int
175175

176176
-- | Perturb a random generator by modifying the current seed
177177
perturbGen :: forall a. Number -> Gen a -> Gen a
178-
perturbGen n (Gen f) = Gen $ \s -> f (s { newSeed = lcgNext (float32ToInt32 n) + s.newSeed })
178+
perturbGen n (Gen f) = Gen $ \s -> f (s { newSeed = perturb s.newSeed })
179+
where
180+
perturb oldSeed = mkSeed (runSeed (lcgNext (mkSeed (float32ToInt32 n))) + runSeed oldSeed)
179181

180182
instance functorGen :: Functor Gen where
181183
map f (Gen g) = Gen $ \s -> case g s of

src/Test/QuickCheck/LCG.purs

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
module Test.QuickCheck.LCG
22
( Seed()
3+
, mkSeed
4+
, runSeed
35
, lcgM
46
, lcgC
57
, lcgN
@@ -16,8 +18,6 @@ import Data.Int (fromNumber, toNumber)
1618
import Data.Int.Bits (shl)
1719
import qualified Data.Maybe.Unsafe as U
1820

19-
type Seed = Int
20-
2121
-- | The *multiplier*: a magic constant for the linear congruential generator
2222
lcgM :: Int
2323
lcgM = 48271
@@ -34,26 +34,41 @@ lcgN :: Int
3434
lcgN = 2147483647
3535

3636
-- | Step the linear congruential generator
37-
lcgNext :: Int -> Int
38-
lcgNext n = U.fromJust $ fromNumber $ (toNumber lcgM * n' + toNumber lcgC) % toNumber lcgN
37+
lcgNext :: Seed -> Seed
38+
lcgNext = Seed <<< go <<< runSeed
3939
where
40-
-- Ensure that the input is between seedMin and seedMax; the LCG will not
41-
-- work well for other inputs.
42-
n' = ensureBetween (toNumber seedMin) (toNumber seedMax) (toNumber n)
43-
44-
ensureBetween :: Number -> Number -> Number -> Number
45-
ensureBetween min max n =
46-
let rangeSize = max - min
47-
in (((n % rangeSize) + rangeSize) % rangeSize) + min
40+
go n = U.fromJust $ fromNumber $ (toNumber lcgM * toNumber n + toNumber lcgC) % toNumber lcgN
4841

4942
-- | Create a random seed
5043
randomSeed :: forall e. Eff (random :: RANDOM | e) Seed
51-
randomSeed = randomInt seedMin seedMax
44+
randomSeed = mkSeed <$> randomInt seedMin seedMax
5245

5346
-- | The minimum permissible Seed value.
54-
seedMin :: Seed
47+
seedMin :: Int
5548
seedMin = 1
5649

5750
-- | The maximum permissible Seed value.
58-
seedMax :: Seed
51+
seedMax :: Int
5952
seedMax = lcgM - 1
53+
54+
-- | A seed for the linear congruential generator. We omit a `Semiring`
55+
-- | instance because there is no `zero` value, as 0 is not an acceptable
56+
-- | seed for the generator.
57+
newtype Seed = Seed Int
58+
59+
mkSeed :: Int -> Seed
60+
mkSeed x = Seed (ensureBetween seedMin seedMax x)
61+
62+
runSeed :: Seed -> Int
63+
runSeed (Seed x) = x
64+
65+
ensureBetween :: Int -> Int -> Int -> Int
66+
ensureBetween min max n =
67+
let rangeSize = max - min
68+
in (((n `mod` rangeSize) + rangeSize) `mod` rangeSize) + min
69+
70+
instance showSeed :: Show Seed where
71+
show (Seed x) = "Seed " <> show x
72+
73+
instance eqSeed :: Eq Seed where
74+
eq (Seed x) (Seed y) = eq x y

0 commit comments

Comments
 (0)